Классы

Геттеры и Сеттеры (Получатели и Установщики)

Классы также позволяют использовать геттеры и сеттеры.

Может быть разумно использовать методы получения и установки для ваших свойств, особенно если вы хотите сделать что-то особенное со значением перед их возвратом или перед их установкой.

Чтобы добавить методы получения и установки (геттеры и сеттеры) в класс, используйте ключевые слова и .

Пример

Создайте геттер и сеттер для свойства «carname»:

class Car {  constructor(brand) {    this.carname
= brand;  }  get cnam() {   
return this.carname;  }  set cnam(x) {   
this.carname = x;  }}mycar = new Car(«Ford»);
document.getElementById(«demo»).innerHTML = mycar.cnam;

Примечание: даже если геттер является методом, вы не используете круглые скобки, когда хотите получить значение свойства.

В этом случае имя геттера/сеттера (метода получения/установки) не может совпадать с именем свойства .

Многие программисты используют символ подчеркивания перед именем свойства, чтобы отделить геттер/сеттер от фактического свойства:

Пример

Вы можете использовать символ подчеркивания, чтобы отделить геттер/сеттер от фактического свойства:

class Car {  constructor(brand) {    this._carname
= brand;  }  get carname() {   
return this._carname;  }  set carname(x) {   
this._carname = x;  }}mycar = new Car(«Ford»);
document.getElementById(«demo»).innerHTML = mycar.carname;

Чтобы использовать setter, используйте тот же синтаксис, что и при установке значения свойства без круглых скобок:

Пример

Используйте сеттер, чтобы изменить название автомобиля на «Volvo»:

class Car {  constructor(brand) {    this._carname
= brand;  }  get carname() {   
return this._carname;  }  set carname(x) {   
this._carname = x;  }}mycar = new Car(«Ford»);
mycar.carname = «Volvo»;
document.getElementById(«demo»).innerHTML = mycar.carname;

Утиная типизация

Альтернативный подход к типу – «утиная типизация», которая основана на одной известной пословице: «If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck (who cares what it really is)».

В переводе: «Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)».

Смысл утиной типизации – в проверке необходимых методов и свойств.

Например, мы можем проверить, что объект – массив, не вызывая , а просто уточнив наличие важного для нас метода, например :

Обратите внимание – в мы не вызываем метод , а пробуем получить само свойство. Для массивов оно всегда есть и является функцией, т.е

даст в логическом контексте .

Проверить на дату можно, определив наличие метода :

С виду такая проверка хрупка, её можно «сломать», передав похожий объект с тем же методом.

Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле).

То есть мы намеренно позволяем передать в код нечто менее конкретное, чем определённый тип, чтобы сделать его более универсальным.

Проверка интерфейса

Если говорить словами «классического программирования», то «duck typing» – это проверка реализации объектом требуемого интерфейса. Если реализует – ок, используем его. Если нет – значит это что-то другое.

9) onReady()

Для инициализации страницы исторически использовалось событие window.onload, которое срабатывает после полной загрузки страницы и всех объектов на ней: счетчиков, картинок и т.п.

Событие — гораздо лучший выбор в 99% случаев. Это событие срабатывает, как только готов DOM документ, до загрузки картинок и других не влияющих на структуру документа объектов.

Это очень удобно, т.к. картинки могут загружаться долго, а обработчик может произвести необходимые изменения на странице и инициализацию интерфейсов тут же, не дожидаясь загрузки всего.

Для добавления обработчика можно использовать следующий кроссбраузерный код:

function bindReady(handler){

	var called = false

	function ready() { // (1)
		if (called) return
		called = true
		handler()
	}

	if ( document.addEventListener ) { // (2)
		document.addEventListener( "DOMContentLoaded", function(){
			ready()
		}, false )
	} else if ( document.attachEvent ) {  // (3)

		// (3.1)
		if ( document.documentElement.doScroll && window == window.top ) {
			function tryScroll(){
				if (called) return
				if (!document.body) return
				try {
					document.documentElement.doScroll("left")
					ready()
				} catch(e) {
					setTimeout(tryScroll, 0)
				}
			}
			tryScroll()
		}

		// (3.2)
		document.attachEvent("onreadystatechange", function(){

			if ( document.readyState === "complete" ) {
				ready()
			}
		})
	}

	// (4)
    if (window.addEventListener)
        window.addEventListener('load', ready, false)
    else if (window.attachEvent)
        window.attachEvent('onload', ready)
    /*  else  // (4.1)
        window.onload=ready
	*/
}
readyList = []

function onReady(handler) {

	if (!readyList.length) {
		bindReady(function() {
			for(var i=0; i<readyList.length; i++) {
				readyList()
			}
		})
	}

	readyList.push(handler)
}

Использование:

onReady(function() {
  // ... 
})

Подробное описание функций , и принципы их работы вы можете почерпнуть в статье Кроссбраузерное событие onDOMContentLoaded.

Изменение встроенных прототипов

Встроенные прототипы можно изменять. Например, если добавить метод к , метод становится доступен для всех строк:

В течение процесса разработки у нас могут возникнуть идеи о новых встроенных методах, которые нам хотелось бы иметь, и искушение добавить их во встроенные прототипы. Это плохая идея.

Важно:

Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют метод , то одна из них перепишет метод другой.

Так что, в общем, изменение встроенных прототипов считается плохой идеей.

В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.

Полифил – это термин, который означает эмуляцию метода, который существует в спецификации JavaScript, но ещё не поддерживается текущим движком JavaScript.

Тогда мы можем реализовать его сами и добавить во встроенный прототип.

Например:

Определение методов

При использовании функций конструктора методы обычно назначаются непосредственно в вместо инициализации, как показано ниже на примере метода .

constructor.js

При использовании классов этот синтаксис упрощается, и метод можно добавить напрямую в класс. Благодаря концепции заблаговременного определения методов, введенной в ES6, определение методов стало еще более быстрой процедурой.

class.js

Давайте посмотрим на эти свойства и методы в действии. Мы создадим новый экземпляр , используя ключевое слово , и присвоим некоторые значения.

Если мы распечатаем дополнительную информацию о нашем новом объекте с помощью команды , мы более подробно увидим. что происходит при инициализации класса.

В результатах мы видим, что функции и были применены к прототипу или объекта , а непосредственно к объекту как к методу. Хотя при создании функций конструктора это очевидно, при создании классов дело обстоит по другому. Классы позволяют использовать более простой и сжатый синтаксис, но при этом немного теряется понятность процесса.

Функция-конструктор

Функции-конструкторы являются обычными функциями. Но есть два соглашения:

  1. Имя функции-конструктора должно начинаться с большой буквы.
  2. Функция-конструктор должна вызываться при помощи оператора .

Например:

Когда функция вызывается как , происходит следующее:

  1. Создаётся новый пустой объект, и он присваивается .
  2. Выполняется код функции. Обычно он модифицирует , добавляет туда новые свойства.
  3. Возвращается значение .

Другими словами, вызов делает примерно вот что:

То есть, результат вызова – это тот же объект, что и:

Теперь, когда нам необходимо будет создать других пользователей, мы можем использовать ,
и т.д. Данная конструкция гораздо удобнее и читабельнее, чем каждый раз создавать литерал объекта. Это и является основной целью конструкторов – удобное повторное создание однотипных объектов.

Ещё раз заметим: технически любая функция может быть использована как конструктор. То есть, каждая функция может быть вызвана при помощи оператора , и выполнится алгоритм, указанный выше в примере. Заглавная буква в названии функции является всеобщим соглашением по именованию, она как бы подсказывает разработчику, что данная функция является функцией-конструктором, и её нужно вызывать через .

new function() { … }

Если в нашем коде большое количество строк, создающих один сложный объект, мы можем обернуть их в функцию-конструктор следующим образом:

Такой конструктор не может быть вызван дважды, так как он нигде не сохраняется, просто создаётся и тут же вызывается. Таким образом, такой метод создания позволяет инкапсулировать код, который создаёт отдельный объект, но без возможности его повторного использования.

Hoisting

Unlike functions, and other JavaScript declarations, class declarations are not hoisted.

That means that you must declare a class before you can use it:

Example

//You cannot use the class yet.//myCar = new Car(«Ford»)//This would
raise an error.class Car { 
constructor(brand) {    this.carname = brand; 
}}//Now you can use the class:let myCar = new Car(«Ford»)

Note: For other declarations, like functions, you will NOT get an
error when you try to use it before it is declared, because the default behavior
of JavaScript declarations are hoisting (moving the declaration to the top).

❮ Previous
Next ❯

What is a class?

So, what exactly is a ? That’s not an entirely new language-level entity, as one might think.

Let’s unveil any magic and see what a class really is. That’ll help in understanding many complex aspects.

In JavaScript, a class is a kind of function.

Here, take a look:

What construct really does is:

  1. Creates a function named , that becomes the result of the class declaration. The function code is taken from the method (assumed empty if we don’t write such method).
  2. Stores class methods, such as , in .

After object is created, when we call its method, it’s taken from the prototype, just as described in the chapter F.prototype. So the object has access to class methods.

We can illustrate the result of declaration as:

Here’s the code to introspect it:

JavaScript

JS Array
concat()
constructor
copyWithin()
entries()
every()
fill()
filter()
find()
findIndex()
forEach()
from()
includes()
indexOf()
isArray()
join()
keys()
length
lastIndexOf()
map()
pop()
prototype
push()
reduce()
reduceRight()
reverse()
shift()
slice()
some()
sort()
splice()
toString()
unshift()
valueOf()

JS Boolean
constructor
prototype
toString()
valueOf()

JS Classes
constructor()
extends
static
super

JS Date
constructor
getDate()
getDay()
getFullYear()
getHours()
getMilliseconds()
getMinutes()
getMonth()
getSeconds()
getTime()
getTimezoneOffset()
getUTCDate()
getUTCDay()
getUTCFullYear()
getUTCHours()
getUTCMilliseconds()
getUTCMinutes()
getUTCMonth()
getUTCSeconds()
now()
parse()
prototype
setDate()
setFullYear()
setHours()
setMilliseconds()
setMinutes()
setMonth()
setSeconds()
setTime()
setUTCDate()
setUTCFullYear()
setUTCHours()
setUTCMilliseconds()
setUTCMinutes()
setUTCMonth()
setUTCSeconds()
toDateString()
toISOString()
toJSON()
toLocaleDateString()
toLocaleTimeString()
toLocaleString()
toString()
toTimeString()
toUTCString()
UTC()
valueOf()

JS Error
name
message

JS Global
decodeURI()
decodeURIComponent()
encodeURI()
encodeURIComponent()
escape()
eval()
Infinity
isFinite()
isNaN()
NaN
Number()
parseFloat()
parseInt()
String()
undefined
unescape()

JS JSON
parse()
stringify()

JS Math
abs()
acos()
acosh()
asin()
asinh()
atan()
atan2()
atanh()
cbrt()
ceil()
clz32()
cos()
cosh()
E
exp()
expm1()
floor()
fround()
LN2
LN10
log()
log10()
log1p()
log2()
LOG2E
LOG10E
max()
min()
PI
pow()
random()
round()
sign()
sin()
sqrt()
SQRT1_2
SQRT2
tan()
tanh()
trunc()

JS Number
constructor
isFinite()
isInteger()
isNaN()
isSafeInteger()
MAX_VALUE
MIN_VALUE
NEGATIVE_INFINITY
NaN
POSITIVE_INFINITY
prototype
toExponential()
toFixed()
toLocaleString()
toPrecision()
toString()
valueOf()

JS OperatorsJS RegExp
constructor
compile()
exec()
g
global
i
ignoreCase
lastIndex
m
multiline
n+
n*
n?
n{X}
n{X,Y}
n{X,}
n$
^n
?=n
?!n
source
test()
toString()

(x|y)
.
\w
\W
\d
\D
\s
\S
\b
\B
\0
\n
\f
\r
\t
\v
\xxx
\xdd
\uxxxx

JS Statements
break
class
continue
debugger
do…while
for
for…in
for…of
function
if…else
return
switch
throw
try…catch
var
while

JS String
charAt()
charCodeAt()
concat()
constructor
endsWith()
fromCharCode()
includes()
indexOf()
lastIndexOf()
length
localeCompare()
match()
prototype
repeat()
replace()
search()
slice()
split()
startsWith()
substr()
substring()
toLocaleLowerCase()
toLocaleUpperCase()
toLowerCase()
toString()
toUpperCase()
trim()
valueOf()

Not just a syntactic sugar

Sometimes people say that is a “syntactic sugar” (syntax that is designed to make things easier to read, but doesn’t introduce anything new), because we could actually declare the same without keyword at all:

The result of this definition is about the same. So, there are indeed reasons why can be considered a syntactic sugar to define a constructor together with its prototype methods.

Still, there are important differences.

  1. First, a function created by is labelled by a special internal property . So it’s not entirely the same as creating it manually.

    The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with :

    Also, a string representation of a class constructor in most JavaScript engines starts with the “class…”

    There are other differences, we’ll see them soon.

  2. Class methods are non-enumerable.
    A class definition sets flag to for all methods in the .

    That’s good, because if we over an object, we usually don’t want its class methods.

  3. Classes always .
    All code inside the class construct is automatically in strict mode.

Besides, syntax brings many other features that we’ll explore later.

Getters and Setters

Classes also allows you to use getters and setters.

It can be smart to use getters and setters for your properties, especially if
you want to do something special with the value before returning them, or before
you set them.

To add getters and setters in the class, use the and keywords.

Example

Create a getter and a setter for the «carname» property:

class Car {  constructor(brand) {    this.carname
= brand;  }  get cnam() {   
return this.carname;  }  set cnam(x) {   
this.carname = x;  }}let myCar = new Car(«Ford»);
document.getElementById(«demo»).innerHTML = myCar.cnam;

Note: even if the getter is a method, you do not use parentheses when you
want to get the property value.

The name of the getter/setter method cannot be the same as the name of the
property, in this case .

Many programmers use an underscore character
before the property name to separate the getter/setter from the actual property:

Example

You can use the underscore character to separate the getter/setter from the
actual property:

class Car {  constructor(brand) {    this._carname
= brand;  }  get carname() {   
return this._carname;  }  set carname(x) {   
this._carname = x;  }}let myCar = new Car(«Ford»);
document.getElementById(«demo»).innerHTML = myCar.carname;

To use a setter, use the same syntax as when you set a property value, without parentheses:

Example

Use a setter to change the carname to «Volvo»:

class Car {  constructor(brand) {    this._carname
= brand;  }  get carname() {   
return this._carname;  }  set carname(x) {   
this._carname = x;  }}let myCar = new Car(«Ford»);
myCar.carname = «Volvo»;
document.getElementById(«demo»).innerHTML = myCar.carname;

Оператор typeof

Оператор возвращает тип аргумента. Это полезно, когда мы хотим обрабатывать значения различных типов по-разному или просто хотим сделать проверку.

У него есть две синтаксические формы:

  1. Синтаксис оператора: .
  2. Синтаксис функции: .

Другими словами, он работает со скобками или без скобок. Результат одинаковый.

Вызов возвращает строку с именем типа:

Последние три строки нуждаются в пояснении:

  1. — это встроенный объект, который предоставляет математические операции и константы. Мы рассмотрим его подробнее в главе Числа. Здесь он служит лишь примером объекта.
  2. Результатом вызова является . Это официально признанная ошибка в , ведущая начало с времён создания JavaScript и сохранённая для совместимости. Конечно, не является объектом. Это специальное значение с отдельным типом.
  3. Вызов возвращает , потому что является функцией. Мы изучим функции в следующих главах, где заодно увидим, что в JavaScript нет специального типа «функция». Функции относятся к объектному типу. Но обрабатывает их особым образом, возвращая . Так тоже повелось от создания JavaScript. Формально это неверно, но может быть удобным на практике.

Классы

Последнее обновление: 11.04.2018

С внедреием стандарта ES2015 (ES6) в JavaScript появился новый способ определения объектов — с помощью классов. Класс представляет описание объекта, его состояния и поведения,
а объект является конкретным воплощением или экземпляром класса.

Для определения класса используется ключевое слово class:

class Person{
}

После слова идет название класса (в данном случае класс называется Person), и затем в фигурных скобках определяется тело класса.

Также можно определить анонимный класс и присвоить его переменной:

let Person = class{}

После этого мы можем создать объекты класса с помощью конструктора:

class Person{}

let tom = new Person();
let bob = new Person();

Для создания объекта с помощью конструктора сначала ставится ключевое слово new. Затем собственно идет вызов конструктора — по сути
вызов функции по имени класса. По умолчанию классы имеют один конструктор без параметров. Поэтому в данном случае при вызове конструктора в него не передается
никаких аргументов.

Также мы можем определить в классе свои конструкторы. Также класс может содержать свойства и методы:

class Person{
	constructor(name, age){
		this.name = name;
		this.age = age;
	}
	display(){
		console.log(this.name, this.age);
	}
}

let tom = new Person("Tom", 34);
tom.display();			// Tom 34
console.log(tom.name);	// Tom

Конструктор определяется с помощью метода с именем constructor. По сути это обычный метод, который может принимать параметры.
Основная цель конструктора — инициализировать объект начальными данными. И в данном случае в конструктор передаются два значения — для имени и возраста пользователя.

Для хранения состояния в классе определяются свойства. Для их определения используется ключевое слово this. В данном случае
в классе два свойства: name и age.

Поведение класса определяют методы. В данном случае определен метод , который выводит значения свойств на консоль.

Поскольку мы определили конструктор, который принимает два параметра, то соответственно мы можем передать в конструктор два значения для инициализации
объекта: .

Наследование

Одни классы могут наследоваться от других. Наследование позволяет сократить объем кода в классах-наследниках. Например, определим следеющие классы:

class Person{
	constructor(name, age){
		this.name = name;
		this.age = age;
	}
	display(){
		console.log(this.name, this.age);
	}
}
class Employee extends Person{
	constructor(name, age, company){
		super(name, age);
		this.company = company;
	}
	display(){
		super.display();
		console.log("Employee in", this.company);
	}
	work(){
		console.log(this.name, "is hard working");
	}
}

let tom = new Person("Tom", 34);
let bob = new Employee("Bob", 36, "Google");
tom.display();
bob.display();
bob.work();

Для наследования одного класса от другого в определении класса применяется оператор extends, после которого идет название базового класса.
То есть в данном случае класс Employee наследуется от класса Person. Класс Person еще называется базовым классом, классом-родителем, суперклассом, а класс
Employee — классом-наследником, подклассом, производным классом.

Производный класс, как и базовый, может определять конструкторы, свойства, методы. Вместе с тем с помощью слова super производный класс может
ссылаться на функционал, определенный в базовом. Например, в конструкторе Employee можно вручную не устанавливать свойства name и age, а с помощью выражения
вызвать конструктор базового класса и тем самым передать работу по установке этих свойств базовому классу.

Подобным образом в методе display в классе Employee через вызов можно обратиться к реализации метода display класса Person.

Консольный вывод программы:

Tom 34
Bob 36
Employee in Google
Bob is hard working

Статические методы

Статические методы вызываются для всего класса вцелом, а не для отедельного объекта. Для их определения применяется оператор static. Например:

class Person{
	constructor(name, age){
		this.name = name;
		this.age = age;
	}
	static nameToUpper(person){
		return person.name.toUpperCase();
	}
	display(){
		console.log(this.name, this.age);
	}
}
let tom = new Person("Tom Soyer", 34);
let personName = Person.nameToUpper(tom);
console.log(personName);		// TOM SOYER

В данном случае определен статический метод . В качестве параметра он принимает объект Person и переводит его имя в верхний
регистр. Поскольку статический метод относится классу вцелом, а не к объекту, то мы НЕ можем использовать в нем ключевое слово this и через него обращаться к свойствам
объекта.

НазадВперед

Class Expression

Подобно функциям
классы можно определять по синтаксису Class Expression в следующем виде:

let Book = class {
         constructor(title, author, price) {
                   this.title = title;
                   this.author = author;
                   this.price = price;
         };
}

Причем,
использовать класс можно только после его объявления как бы он ни был объявлен.
В отличие от функций, которые можно вызывать в скрипте до их определения, если
они заданы по синтаксису Function Declaration.

Также, подобно
функциям, классам можно назначать имя при следующем определении:

let Book = class BookClass {
         constructor(title, author, price) {
                   this.title = title;
                   this.author = author;
                   this.price = price;
         };
}

Причем, это
второе имя будет видно только внутри этого класса. За его пределами можно
обращаться только через переменную Book.

Интересной
особенностью является возможность создавать классы динамически с помощью
функции, например, так:

function createFruit(name, weight) {
         return class {
                   constructor() {
                            this.name = name;
                            this.weight = weight;
                   };
 
                   showInfo() { console.log(this.name + " " + this.weight) };
         }
}

Здесь описана
функция, которая формирует новый класс со значениями параметров name и weight, которые будут
записаны в объект при его создании. Далее, мы можем создать класс для
определенного фрукта:

let f1 = createFruit("Яблоко", 100);

Причем, здесь f1 – это ссылка
на класс, а не на созданный объект, то есть, далее, нужно вызвать оператор new и создать
объект:

let apple = new f1();
apple.showInfo();

Если вызвать
функцию еще раз с другими параметрами:

let f2 = createFruit("Вишня", 40);

то будет создан
еще один класс, никак не связанный с предыдущим и, далее, также можно выполнить
следующие конструкции:

let v = new f2();
v.showInfo();

То есть, все
классы работают совершенно независимо.

Class Inheritance

To create a class inheritance, use the
keyword.

A class created with a class inheritance inherits all the methods from
another class:

Example

Create a class named «Model» which will inherit the methods from the «Car»
class:

class Car {  constructor(brand) {    this.carname =
brand; 
}  present() {    return ‘I have a ‘ + this.carname; 
}}class Model extends Car { 
constructor(brand, mod) {    super(brand);    this.model = mod; 
}  show() {   
return this.present() + ‘, it is a ‘ + this.model; 
}}let myCar = new Model(«Ford», «Mustang»);document.getElementById(«demo»).innerHTML
= myCar.show();

The method refers to the parent
class.

By calling the method in the
constructor method, we call the parent’s constructor method and gets access to
the parent’s properties and methods.

Inheritance is useful for code reusability: reuse properties and methods of an existing class when you create a new class.

Class Expression

Также, как и Function Expression, классы можно задавать «инлайн», в любом выражении и внутри вызова функции.

Это называется Class Expression:

В примере выше у класса нет имени, что один-в-один соответствует синтаксису функций. Но имя можно дать. Тогда оно, как и в Named Function Expression, будет доступно только внутри класса:

В примере выше имя будет доступно только внутри класса и может быть использовано, например, для создания новых объектов данного типа.

Наиболее очевидная область применения этой возможности – создание вспомогательного класса прямо при вызове функции.

Например, функция в примере ниже создаёт объект по классу и данным, добавляет ему и пишет в «реестр» :

Итого

Для написания полиморфных (это удобно!) функций нам нужна проверка типов.

  • Для примитивов с ней отлично справляется оператор .

    У него две особенности:

    • Он считает объектом, это внутренняя ошибка в языке.
    • Для функций он возвращает , по стандарту функция не считается базовым типом, но на практике это удобно и полезно.
  • Для встроенных объектов мы можем получить тип из скрытого свойства , при помощи вызова . Для конструкторов, которые объявлены нами, всегда равно .

  • Оператор проверяет, создан ли объект функцией , работает для любых конструкторов. Более подробно мы разберём его в главе Проверка класса: «instanceof».

  • И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется «утиная типизация».

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector