es2015
|
@ -62,11 +62,15 @@
|
|||
|
||||
# Примеры на этом сайте
|
||||
|
||||
[warn header="Только при поддержке браузера"]
|
||||
Запускаемые примеры с ES-2015 будут работать только если ваш браузер поддерживает соответствующую возможность стандарта.
|
||||
[/warn]
|
||||
|
||||
Это означает, что при запуске примеров в браузере, который их не поддерживает, будет ошибка. Это не означает, что пример неправильный! Просто пока нет поддержки...
|
||||
|
||||
Рекомендуется [Chrome Canary](https://www.google.com/chrome/browser/canary.html), Edge или [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/channel/#developer).
|
||||
|
||||
Впрочем, если пример в браузере не работает (обычно проявляется как ошибка синтаксиса) -- вы можете запустить его при помощи Babel, на странице [Babel: try it out](https://babeljs.io/repl/). Там же увидите и преобразованный код.
|
||||
Впрочем, если пример в браузере не работает (обычно проявляется как ошибка синтаксиса) -- почти все примеры вы можете запустить его при помощи Babel, на странице [Babel: try it out](https://babeljs.io/repl/). Там же увидите и преобразованный код.
|
||||
|
||||
На практике для кросс-браузерности всё равно используют Babel.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
@ -362,7 +362,7 @@ function f() {
|
|||
f(1); // 1
|
||||
```
|
||||
|
||||
Выведется `1` из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` берутся из внешней функции.
|
||||
Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` берутся из внешней функции.
|
||||
|
||||
Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов.
|
||||
|
||||
|
@ -403,11 +403,24 @@ function defer(f, ms) {
|
|||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
Основные улучшения в функциях:
|
||||
<ul>
|
||||
<li>Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.</li>
|
||||
<li>Оператор spread (троеточие) в объявлении позволяет функции получать оставшиеся аргументы в массив: `function f(arg1, arg2, ...rest)`.</li>
|
||||
<li>Тот же оператор spread в вызове функции позволяет передать её массив как список аргументов (вместо `apply`).</li>
|
||||
<li>У функции есть свойство `name`, оно содержит имя, указанное при объявлении функции, либо, если его нет, то имя свойства или переменную, в которую она записана. Есть и некоторые другие ситуации, в которых интерпретатор подставляет "самое подходящее" имя.</li>
|
||||
<li>Объявление Function Declaration в блоке `{...}` видно только в этом блоке.</li>
|
||||
<li>Появились функции-стрелки:
|
||||
<ul>
|
||||
<li>Без фигурных скобок возвращают выражение `expr`: `(args) => expr`.</li>
|
||||
<li>С фигурными скобками требуют явного `return`.</li>
|
||||
<li>Сохраняют `this` и `arguments` окружающего контекста.</li>
|
||||
<li>Не могут быть использованы как конструкторы, с `new`.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
|
289
1-js/10-es-modern/6-es-objects/article.md
Normal file
|
@ -0,0 +1,289 @@
|
|||
|
||||
# Объекты и прототипы
|
||||
|
||||
В этом разделе мы рассмотрим нововведения, которые касаются именно объектов.
|
||||
|
||||
По классам -- чуть позже, в отдельном разделе, оно того заслуживает.
|
||||
|
||||
|
||||
Для объектов есть очень приятные нововведения, которые касаются объявления свойств и методов. Новый синтаксис используется и в классах.
|
||||
|
||||
Нововведения в объектах и классах очень тесно взаимосвязаны, поэтому обе этих темы объединены в один раздел.
|
||||
|
||||
Мы начнём с объектов, а затем перейдём к классам.
|
||||
|
||||
|
||||
## Короткое свойство
|
||||
|
||||
Зачастую у нас есть переменные, например, `name` и `isAdmin`, и мы хотим использовать их в объекте.
|
||||
|
||||
При объявлении объекта в этом случае достаточно указать только имя свойства, а значение будет взято из переменной с таким именем.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let name = "Вася";
|
||||
let isAdmin = true;
|
||||
|
||||
*!*
|
||||
let user = {
|
||||
name,
|
||||
isAdmin
|
||||
};
|
||||
*/!*
|
||||
alert( JSON.stringify(user) ); // {"name": "Вася", "isAdmin": true}
|
||||
```
|
||||
|
||||
|
||||
## Вычисляемые свойства
|
||||
|
||||
В качестве имени свойства можно использовать выражение, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let propName = "firstName";
|
||||
|
||||
let user = {
|
||||
*!*
|
||||
[propName]: "Вася"
|
||||
*/!*
|
||||
};
|
||||
|
||||
alert( user.firstName ); // Вася
|
||||
```
|
||||
|
||||
Или даже так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let a = "Мой ";
|
||||
let b = "Зелёный ";
|
||||
let c = "Крокодил";
|
||||
|
||||
let user = {
|
||||
*!*
|
||||
[(a + b + c).toLowerCase()]: "Вася"
|
||||
*/!*
|
||||
};
|
||||
|
||||
alert( user["мой зелёный крокодил"] ); // Вася
|
||||
```
|
||||
|
||||
|
||||
## Геттер-сеттер для прототипа
|
||||
|
||||
В ES5 для прототипа был метод-геттер:
|
||||
<ul>
|
||||
<li>`Object.getPrototypeOf(obj)`</li>
|
||||
</ul>
|
||||
|
||||
В современной JavaScript также добавился сеттер:
|
||||
<ul>
|
||||
<li>`Object.setPrototypeOf(obj, newProto)`</li>
|
||||
</ul>
|
||||
|
||||
...А также "узаконено" свойство `__proto__`, которое даёт прямой доступ к прототипу. Его, в качестве "нестандартного", но удобного способа работы с прототипом реализовали почти все браузеры (кроме IE10-), так что было принято решение добавить его в стандарт.
|
||||
|
||||
По стандарту оно реализовано через геттеры-сеттеры `Object.getPrototypeOf/setPrototypeOf`.
|
||||
|
||||
|
||||
## Object.assign
|
||||
|
||||
Синтаксис:
|
||||
```js
|
||||
Object.assign(target, src1, src2...)
|
||||
```
|
||||
|
||||
Функция `Object.assign` получает список объектов и копирует в первый `target` свойства из остальных.
|
||||
|
||||
Последующие свойства перезаписывают предыдущие.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let user = { name: "Вася" };
|
||||
let visitor = { isAdmin: false, visits: true };
|
||||
let admin = { isAdmin: true };
|
||||
|
||||
Object.assign(user, visitor, admin);
|
||||
|
||||
alert( JSON.stringify(user) ); // user: Вася, visits: true, isAdmin: true
|
||||
```
|
||||
|
||||
|
||||
## Методы объекта
|
||||
|
||||
Долгое время в JavaScript термин "метод объекта" был просто альтернативным названием для свойства-функции.
|
||||
|
||||
Теперь это уже не так, добавлены именно "методы объекта". Они отличаются от обычных свойств-функций наличием специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит.
|
||||
|
||||
Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let name = "Вася";
|
||||
let user = {
|
||||
name,
|
||||
*!*
|
||||
/* вместо sayHi: function() { */
|
||||
sayHi() {
|
||||
alert(this.name);
|
||||
}
|
||||
*/!*
|
||||
};
|
||||
|
||||
user.sayHi(); // Вася
|
||||
```
|
||||
|
||||
Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let name = "Вася", surname="Петров";
|
||||
let user = {
|
||||
name,
|
||||
surname,
|
||||
get fullName() {
|
||||
return `${name} ${surname}`;
|
||||
}
|
||||
};
|
||||
|
||||
alert( user.fullName ); // Вася Петров
|
||||
```
|
||||
|
||||
Основное отличие "методов" от "просто функций" -- возможность обратиться к "родительскому методу" через ключевое слово `super`, о котором пойдёт речь дальше.
|
||||
|
||||
|
||||
## super
|
||||
|
||||
Ссылка `super` позволяет из метода обратиться к прототипу объекта.
|
||||
|
||||
Например, в коде ниже `super.walk` из `rabbit` обращается к `animal.walk`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let animal = {
|
||||
walk() {
|
||||
alert("I'm walking");
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
walk() {
|
||||
*!*
|
||||
alert(super.walk); // walk() { … }
|
||||
super.walk(); // I'm walking
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
rabbit.walk();
|
||||
```
|
||||
|
||||
При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов.
|
||||
|
||||
Например, тот же код, но со свойством-функцией `walk` вместо метода в `rabbit`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let animal = {
|
||||
walk() {
|
||||
alert("I'm walking");
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
*!*
|
||||
walk: function() {
|
||||
super.walk(); // Будет ошибка!
|
||||
}
|
||||
*/!*
|
||||
};
|
||||
|
||||
rabbit.walk();
|
||||
```
|
||||
|
||||
Будет ошибка, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`.
|
||||
|
||||
Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции.
|
||||
|
||||
Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`:
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let animal = {
|
||||
walk() {
|
||||
alert("I'm walking");
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
walk() {
|
||||
*!*
|
||||
setTimeout(() => super.walk()); // I'm walking
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
rabbit.walk();
|
||||
```
|
||||
|
||||
[smart header="Свойство `[[HomeObject]]` -- не изменяемое"]
|
||||
|
||||
При создании метода -- он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить независимо:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let animal = {
|
||||
walk() { alert("I'm walking"); }
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
walk() {
|
||||
super.walk();
|
||||
alert(this);
|
||||
}
|
||||
};
|
||||
|
||||
let walk = rabbit.walk; // скопируем метод в переменную
|
||||
*!*
|
||||
walk();
|
||||
// I'm walking
|
||||
// undefined
|
||||
*/!*
|
||||
```
|
||||
|
||||
В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`.
|
||||
|
||||
Это относится именно к `super`. Правила `this` для методов те же, в примере выше будет `undefined`.
|
||||
[/smart]
|
||||
|
169
1-js/10-es-modern/7-symbol/article.md
Normal file
|
@ -0,0 +1,169 @@
|
|||
|
||||
# Тип данных Symbol
|
||||
|
||||
Новый примитивный тип данных Symbol служит для создания уникальных идентификаторов.
|
||||
|
||||
|
||||
## Объявление
|
||||
|
||||
Синтаксис:
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let name = Symbol();
|
||||
alert( typeof name ); // symbol
|
||||
```
|
||||
|
||||
Обратим внимание, не `new Symbol`, а просто `Symbol`, так как это -- примитив.
|
||||
|
||||
Каждый символ -- уникален.
|
||||
|
||||
У функции `Symbol` есть необязательный аргумент "имя символа". Можно его использовать для описания символа, в целях отладки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let name = Symbol("name");
|
||||
alert( name.toString() ); // Symbol(name)
|
||||
```
|
||||
|
||||
При этом если у двух символов одинаковое имя, то они *не равны*:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( Symbol("name") == Symbol("name") ); // false
|
||||
```
|
||||
|
||||
То есть, ещё раз заметим, что каждый символ -- уникален.
|
||||
|
||||
## Глобальные символы
|
||||
|
||||
Существует "глобальный реестр" символов, который позволяет, при необходимости, разделять символы между частями программы.
|
||||
|
||||
Для чтения (или создания, если нет) "глобального" символа служит вызов `Symbol.for(имя)`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
// создание символа в реестре
|
||||
let name = Symbol.for("name");
|
||||
|
||||
// символ уже есть, чтение из реестра
|
||||
alert( Symbol.for("name") == name ); // true
|
||||
```
|
||||
|
||||
Вызов `Symbol.keyFor(sym)` позволяет получить по глобальному символу его имя:
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
// создание символа в реестре
|
||||
let name = Symbol.for("name");
|
||||
|
||||
// получение имени символа
|
||||
alert( Symbol.keyFor(name) ); // name
|
||||
```
|
||||
[warn header="`Symbol.keyFor` возвращает `undefined`, если символ не глобальный"]
|
||||
Заметим, что `Symbol.keyFor` работает *только для глобальных символов*, для остальных будет возвращено `undefined`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
alert( Symbol.keyFor(Symbol.for("name")) ); // name, глобальный
|
||||
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный символ
|
||||
```
|
||||
[/warn]
|
||||
|
||||
## Использование символов
|
||||
|
||||
Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let isAdmin = Symbol("isAdmin");
|
||||
|
||||
let user = {
|
||||
name: "Вася",
|
||||
age: 30,
|
||||
[isAdmin]: true
|
||||
};
|
||||
|
||||
alert( Object.keys(user) ); // name, age
|
||||
for(let key in user) alert(key); // name, age
|
||||
```
|
||||
|
||||
В примере выше выведутся все свойства, кроме символьного.
|
||||
|
||||
Это нужно в первую очередь для различных системных свойств, которых в современном JavaScript очень много.
|
||||
|
||||
Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1).
|
||||
|
||||
В спецификации принято для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`.
|
||||
|
||||
Например:
|
||||
<ul>
|
||||
<li>`Symbol.toPrimitive` -- идентификатор для свойства, задающего функцию преобразования объекта в примитив.</li>
|
||||
<li>`Symbol.iterator` -- идентификатор для свойства, задающего функцию итерации по объекту.</li>
|
||||
<li>...и т.п.</li>
|
||||
</ul>
|
||||
|
||||
Смысл здесь в том, что допустим надо добавить к объекту "особый" функционал, например преобразование к примитиву или функцию итерации, или ещё что-то...
|
||||
|
||||
Новый стандарт не мог просто сказать, что "свойство obj.toPrimitive теперь системное, оно делает то-то и то-то". Ведь свойство с таким именем, вполне возможно, используется в существующем коде. И он сломался бы.
|
||||
|
||||
Поэтому ввели целый тип "символы", которые можно использовать для задания свойств, которые уникальны, не участвуют в итерации и заведомо не конфликтуют со старым кодом.
|
||||
|
||||
Например:
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let obj = {
|
||||
iterator: 1,
|
||||
[Symbol.iterator]: function() {}
|
||||
}
|
||||
|
||||
alert(obj.iterator); // 1, символ не конфликтует
|
||||
```
|
||||
|
||||
Чтобы получить символы, есть особый вызов [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols).
|
||||
|
||||
Эта функция возвращает все символы в объекте. Заметим, что `getOwnPropertyNames` символы не возвращает.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let obj = {
|
||||
iterator: 1,
|
||||
[Symbol.iterator]: function() {}
|
||||
}
|
||||
|
||||
// один символ в объекте
|
||||
alert( Object.getOwnPropertySymbols(obj) ); // Symbol(Symbol.iterator)
|
||||
```
|
||||
|
||||
|
||||
|
||||
Один из самых известных и полезных символов -- это `Symbol.iterator`. Мы будем активно его использовать позже, в главе про итераторы.
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Символы -- новый примитивный тип, предназначенный для уникальных идентификаторов.</li>
|
||||
<li>Все символы уникальны, символы с одинаковым именем не равны друг другу.</li>
|
||||
<li>Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`.</li>
|
||||
<li>Основная область использования символов -- это системные свойства объектов. Поддержка у них пока небольшая, но она растёт. Символы позволяют добавлять в стандарт новые "особые" свойства объектов, при этом не резервируя соответствующие названия.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
325
1-js/10-es-modern/8-es-classes/article.md
Normal file
|
@ -0,0 +1,325 @@
|
|||
|
||||
# Классы
|
||||
|
||||
В современном JavaScript появился новый, "более красивый" синтаксис для классов. Он естественным образом продолжает синтаксис для объектов и методов, который мы рассмотрели раньше.
|
||||
|
||||
## Class
|
||||
|
||||
Новая конструкция `class` -- удобный "синтаксический сахар" для задания конструктора вместе с прототипом.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
class User {
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
sayHi() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let user = new User("Вася");
|
||||
user.sayHi(); // Вася
|
||||
```
|
||||
|
||||
Это объявление примерно аналогично такому:
|
||||
|
||||
```js
|
||||
function User(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
User.prototype.sayHi = function() {
|
||||
alert(this.name);
|
||||
};
|
||||
```
|
||||
|
||||
В обоих случаях `new User` будет создавать объекты. Метод `sayHi` -- также в обоих случаях находится в прототипе.
|
||||
|
||||
Но есть и отличия при объявлении через `class`:
|
||||
|
||||
<ul>
|
||||
<li>`User`, объявленный как класс, нельзя вызывать без `new`, будет ошибка.</li>
|
||||
<li>Объявление класса с точки зрения области видимости ведёт себя как `let`.</li>
|
||||
</ul>
|
||||
|
||||
Методы, объявленные внутри `class`, также имеют ряд особенностей:
|
||||
|
||||
<ul>
|
||||
<li>Метод `sayHi` является именно методом, то есть имеет доступ к `super`.</li>
|
||||
<li>Все методы класса работают в `use strict`, даже если он не указан.</li>
|
||||
<li>Все методы класса не перечислимы, то есть в `for..in` по объекту их не будет.</li>
|
||||
</ul>
|
||||
|
||||
## Class Expression
|
||||
|
||||
Так же, как и Function Expression, классы можно задавать "инлайн" в выражении.
|
||||
|
||||
Это называется Class Expression:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let User = class {
|
||||
sayHi() { alert('Привет!'); }
|
||||
};
|
||||
|
||||
new User().sayHi();
|
||||
```
|
||||
|
||||
В примере выше у класса нет имени, что один-в-один соответствует синтаксису функций. Но имя можно дать. Тогда оно, как и в Named Function Expression, будет доступно только внутри класса:
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let SiteGuest = class User {
|
||||
sayHi() { alert('Привет!'); }
|
||||
};
|
||||
|
||||
new SiteGuest().sayHi(); // Привет
|
||||
*!*
|
||||
new User(); // ошибка
|
||||
*/!*
|
||||
```
|
||||
|
||||
В примере выше имя `User` будет доступно только внутри класса и может быть использовано, например для создания новых объектов данного типа.
|
||||
|
||||
## Геттеры, сеттеры и вычисляемые свойства
|
||||
|
||||
В классах, как и в обычных объектах, можно объявлять геттеры и сеттеры через `get/set`, а также использовать `[…]` для свойств с вычисляемыми именами:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
class User {
|
||||
constructor(firstName, lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
// геттер
|
||||
*!*
|
||||
get fullName() {
|
||||
return `${this.firstName} ${this.lastName}`;
|
||||
}
|
||||
*/!*
|
||||
|
||||
// сеттер
|
||||
*!*
|
||||
set fullName(newValue) {
|
||||
[this.firstName, this.lastName] = newValue.split(' ');
|
||||
}
|
||||
*/!*
|
||||
|
||||
["test".toUpperCase()]: true
|
||||
|
||||
};
|
||||
|
||||
let user = new User("Вася", "Пупков");
|
||||
alert( user.fullName ); // Вася Пупков
|
||||
user.fullName = "Иван Петров";
|
||||
alert( user.fullName ); // Иван Петров
|
||||
alert( user.TEST ); // true
|
||||
```
|
||||
|
||||
При чтении `fullName` будет вызван метод `get fullName()`, при присвоении -- метод `set fullName` с новым значением.
|
||||
|
||||
## Статические свойства
|
||||
|
||||
Статические свойства класса -- это свойства непосредственно класса `User`.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
class User {
|
||||
constructor(firstName, lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
*!*
|
||||
static createGuest() {
|
||||
return new User("Гость", "Сайта");
|
||||
}
|
||||
*/!*
|
||||
};
|
||||
|
||||
let user = User.createGuest();
|
||||
|
||||
alert( user.firstName ); // Гость
|
||||
|
||||
alert( User.createGuest ); // createGuest ... (функция)
|
||||
```
|
||||
|
||||
Как правило, они используются для операций, не требующих наличия объекта, например -- для фабричных, как в примере выше, то есть как альтернативные варианты конструктора.
|
||||
|
||||
Или же, можно добавить метод `User.compare`, который будет сравнивать двух пользователей для целей сортировки.
|
||||
|
||||
## Наследование
|
||||
|
||||
Синтаксис:
|
||||
```js
|
||||
class Child extends Parent {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
В примере ниже объявлено два класса: `Animal` и наследующий от него `Rabbit`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
class Animal {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
walk() {
|
||||
alert("I walk: " + this.name);
|
||||
}
|
||||
}
|
||||
|
||||
*!*
|
||||
class Rabbit extends Animal {
|
||||
*/!*
|
||||
walk() {
|
||||
super.walk();
|
||||
alert("...and jump!");
|
||||
}
|
||||
}
|
||||
|
||||
new Rabbit("Вася").walk();
|
||||
// I walk: Вася
|
||||
// and jump!
|
||||
```
|
||||
|
||||
[smart header="Обычные прототипы"]
|
||||
При наследовании формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
class Animal { }
|
||||
class Rabbit extends Animal { }
|
||||
|
||||
alert( Rabbit.prototype.__proto__ == Animal.prototype ); // true
|
||||
```
|
||||
[/smart]
|
||||
|
||||
Как видно из примера выше, методы родителя можно переопределить в наследнике. При этом для обращения к родительскому методу используют `super.method()`.
|
||||
|
||||
Немного особая история -- с конструктором.
|
||||
|
||||
Конструктор `constructor` родителя наследуется автоматически. То есть, если в потомке не указан свой `constructor`, то используется родительский.
|
||||
|
||||
Если его переопределить, то родительский конструктор вызывается через `super()`, а не через `super.constructor()`.
|
||||
|
||||
Например, вызовем конструктор `Animal` в `Rabbit`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
class Animal {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
walk() {
|
||||
alert("I walk: " + this.name);
|
||||
}
|
||||
}
|
||||
|
||||
class Rabbit extends Animal {
|
||||
*!*
|
||||
constructor() {
|
||||
// вызвать конструктор Animal с аргументом "Кроль"
|
||||
super("Кроль"); // то же, что и Animal.apply(this, arguments)
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
|
||||
new Rabbit().walk(); // I walk: Кроль
|
||||
```
|
||||
|
||||
...Однако, здесь есть небольшие ограничения:
|
||||
<ul>
|
||||
<li>Вызвать конструктор родителя можно только изнутри конструктора потомка. В частности, `super()` нельзя вызвать из произвольного метода.</li>
|
||||
<li>В конструкторе потомка мы обязаны вызвать `super()` до обращения к `this`. До вызова `super` не существует `this`, так как по спецификации в этом случае именно `super` инициализует `this`.</li>
|
||||
</ul>
|
||||
|
||||
Второе ограничение выглядит несколько странно, поэтому проиллюстрируем его примером:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
class Animal {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
class Rabbit extends Animal {
|
||||
*!*
|
||||
constructor() {
|
||||
alert(this); // ошибка, this не определён!
|
||||
// обязаны вызвать super() до обращения к this
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
|
||||
new Rabbit();
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
Концепция классов, которая после долгих обсуждений получилась в стандарте EcmaScript, носит название "максимально минимальной". То есть, в неё вошли только те возможности, которые уж точно необходимы.
|
||||
|
||||
В частности, не вошли "приватные" и "защищённые" свойства. То есть, все свойства и методы класса технически доступны снаружи. Возможно, они появятся в будущих редакциях стандарта.
|
||||
|
||||
|
||||
В классах нет возможности ограничивать
|
||||
|
||||
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
```js
|
||||
function PropertyError(property) {
|
||||
Error.call(this, property) ;
|
||||
this.name = "PropertyError";
|
||||
|
||||
this.property = property;
|
||||
|
@ -71,10 +72,10 @@ Error.captureStackTrace(this, PropertyError);
|
|||
</dd>
|
||||
</dl>
|
||||
|
||||
[smart header="Конструктор родителя здесь не нужен"]
|
||||
Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядел бы как `Error.call(this, message)`.
|
||||
[smart header="Конструктор родителя здесь не обязателен"]
|
||||
Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядит как `Error.call(this, message)`.
|
||||
|
||||
Однако, встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Поэтому и вызывать его здесь нет необходимости.
|
||||
Строго говоря, этот вызов здесь не обязателен. Встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Единственный возможный смысл его вызова -- он ставит специальное внутреннее свойство `[[ErrorData]]`, которое выводится в `toString` и позволяет увидить, что это ошибка. Поэтому по стандарту вызывать конструктор `Error` при наследовании в таких случаях рекомендовано.
|
||||
[/smart]
|
||||
|
||||
|
||||
|
|