This commit is contained in:
Ilya Kantor 2015-07-08 09:53:06 +03:00
parent ff23414aed
commit 5bb3c5892a
7 changed files with 230 additions and 79 deletions

View file

@ -231,6 +231,7 @@ set.forEach((value, valueAgain, set) => {
Если поместить такие данные в `WeakMap`, а объект сделать ключом, то они будут автоматически удалены из памяти, когда удалится элемент. Если поместить такие данные в `WeakMap`, а объект сделать ключом, то они будут автоматически удалены из памяти, когда удалится элемент.
Например: Например:
```js ```js
// текущие активные пользователи // текущие активные пользователи
let activeUsers = [ let activeUsers = [
@ -248,7 +249,7 @@ weakMap[activeUsers[0]] = 1;
weakMap[activeUsers[1]] = 2; weakMap[activeUsers[1]] = 2;
weakMap[activeUsers[2]] = 3; weakMap[activeUsers[2]] = 3;
alert( weakMap[activeUsers[0]].name ); // Вася alert( weakMap[activeUsers[0]] ); // 1
activeUsers.splice(0, 1); // Вася более не активный пользователь activeUsers.splice(0, 1); // Вася более не активный пользователь
@ -259,8 +260,30 @@ activeUsers.splice(0, 1); // Петя более не активный поль
// weakMap теперь содержит только 1 элемент // weakMap теперь содержит только 1 элемент
``` ```
TODO WRITE MORE У WeakMap есть ряд ограничений:
<ul>
<li>Нет свойства `size`.</li>
<li>Нельзя перебрать элементы итератором или `forEach`.</li>
<li>Нет метода `clear()`.</li>
</ul>
Иными словами, `WeakMap` работает только на запись (`set`, `delete`) и чтение (`get`, `has`) элементов по конкретному ключу, а не как полноценная коллекция. Нельзя вывести всё содержимое `WeakMap`, нет соответствующих методов.
Это связано с тем, что содержимое `WeakMap` может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. Нет какого-то конкретного момента, когда такая очистка точно произойдёт -- это определяется внутренними алгоритмами сборщика и его сведениями о системе.
Поэтому содержимое `WeakMap` в произвольный момент, строго говоря, не определено. Может быть, сборщик мусора уже удалил какие-то записи, а может и нет. С этим, а также с требованиями к эффективной реализации `WeakMap`, и связано отсутствие методов, осуществляющих доступ ко всем записям.
То же самое относится и к `WeakSet`: можно добавлять элементы, проверять их наличие, но нельзя получить их список и даже узнать количество.
Эти ограничения могут показаться неудобными, но по сути они не мешают `WeakMap/WeakSet` выполнять свою основную задачу -- быть "вторичным" хранилищем данных для объектов, актуальный список которых (и сами они) хранятся в каком-то другом месте.
## Итого
<ul>
<li>`Map` -- коллекция записей вида `ключ: значение`, лучше `Object` тем, что перебирает всегда в порядке вставки и допускает любые ключи.</li>
<li>`Set` -- коллекция уникальных элементов, также допускает любые ключи.
<li>`WeakMap` и `WeakSet` -- "урезанные" по функционалу варианты `Map/Set`, которые позволяют только "точечно" обращаться элементам (по конкретному ключу или значению). Они не препятствуют сборке мусора, то есть если ссылка на объект осталась только в `WeakSet/WeakMap` -- он будет удалён.</li>
</ul>

View file

@ -24,7 +24,9 @@ alert(lastName); // Кантор
//+ run //+ run
'use strict'; 'use strict';
*!*
let [firstName, lastName, ...rest] = "Юлий Цезарь Император Рима".split(" "); let [firstName, lastName, ...rest] = "Юлий Цезарь Император Рима".split(" ");
*/!*
alert(firstName); // Юлий alert(firstName); // Юлий
alert(lastName); // Цезарь alert(lastName); // Цезарь
@ -33,6 +35,59 @@ alert(rest); // Император,Рима (массив из 2х элем
Значением `rest` будет массив из оставшихся элементов массива. Значением `rest` будет массив из оставшихся элементов массива.
**Можно задать и значения по умолчанию, если массив почему-то оказался короче, чем ожидалось.**
Они задаются через знак `=`, например:
```js
//+ run
'use strict';
*!*
let [firstName="Гость", lastName="Анонимный"] = [];
*/!*
alert(firstName); // Гость
alert(lastName); // Анонимный
```
В качестве значений по умолчанию можно использовать не только примитивы, но и выражения, содержащие вызовы функций:
```js
//+ run
'use strict';
function defaultLastName() {
return Date.now() + '-visitor';
}
*!*
// lastName получит значение, соответствующее текущей дате:
let [firstName, lastName=defaultLastName()] = ["Вася"];
*/!*
alert(firstName); // Вася
alert(lastName); // 1436...-visitor
```
Заметим, что вызов функции `defaultLastName` будет осуществлён только при необходимости, то есть если значения нет в массиве.
**Ненужные элементы массива можно отбросить, поставив лишнюю запятую:**
```js
//+ run
'use strict';
*!*
// первый и второй элементы не нужны
let [, , title] = "Юлий Цезарь Император Рима".split(" ");
*/!*
alert(title); // Император
```
В коде выше первый и второй элементы массива никуда не записались, они были отброшены. Как, впрочем, и все элементы после третьего.
## Деструктуризация объекта ## Деструктуризация объекта
Деструктуризация может "мгновенно" разобрать объект по переменным. Деструктуризация может "мгновенно" разобрать объект по переменным.
@ -58,7 +113,7 @@ alert(`${title} ${width} ${height}`); // Меню 100 200
Как видно, свойства автоматически присваиваются соответствующим переменным. Как видно, свойства автоматически присваиваются соответствующим переменным.
Если хочется присвоить свойство объекта другой переменной, можно указать соответствие через двоеточие: Если хочется присвоить свойство объекта в переменную с другим именем, можно указать соответствие через двоеточие, вот так:
```js ```js
//+ run //+ run
@ -79,7 +134,7 @@ alert(`${title} ${w} ${h}`); // Меню 100 200
В примере выше свойство `width` отправилось в переменную `w`, свойство `height` -- в переменную `h`, а `title` -- в переменную с тем же названием. В примере выше свойство `width` отправилось в переменную `w`, свойство `height` -- в переменную `h`, а `title` -- в переменную с тем же названием.
Если каких-то свойств в объекте нет,можно указать значение по умолчанию через знак равенства `=`, вот так; Если каких-то свойств в объекте нет, можно указать значение по умолчанию через знак равенства `=`, вот так;
```js ```js
//+ run //+ run
@ -116,7 +171,7 @@ alert(`${title} ${w} ${h}`); // Меню 100 200
А что, если в объекте больше значений, чем переменных? Можно ли куда-то присвоить "остаток"? А что, если в объекте больше значений, чем переменных? Можно ли куда-то присвоить "остаток"?
Такая возможность планируется в будущем стандарте ES-2016, и выглядеть она будет так: Такой возможности в текущем стандарте нет. Она планируется в будущем стандарте, и выглядеть она будет аналогично массивам:
```js ```js
//+ run //+ run
@ -136,9 +191,7 @@ let {title, ...size} = menuOptions;
// size = { width: 100, height: 200} (остаток) // size = { width: 100, height: 200} (остаток)
``` ```
Ещё раз заметим, что аналогичный массивам оператор `...` ("spread") для объектов пока не стандарт. Этот код будет работать, например, при использовании Babel со включёнными экспериментальными возможностями, но ещё раз заметим, что в текущий стандарт такая возможность не вошла.
Этот код будет работать, например, при использовании Babel со включёнными экспериментальными возможностями.
[smart header="Деструктуризация без объявления"] [smart header="Деструктуризация без объявления"]
@ -208,14 +261,14 @@ alert(`${title} ${width} ${height} ${item1} ${item2}`);
<li>Синтаксис: <li>Синтаксис:
```js ```js
let {prop : varName = default, ...} = object let {prop : varName = default, ...} = object
let [var1, var2, ...rest] = array let [var1=default, var2, ...rest] = array
``` ```
Здесь двоеточие `:` задаёт отображение свойства в переменную, а `=` задаёт значение по умолчанию (если нужно). Здесь двоеточие `:` задаёт отображение свойства в переменную, а `=` задаёт выражение, которое будет использовано, если значение отсутствует (не указано или `undefined`).
Объявление переменной вначале не обязательно, если переменные уже есть, но без него при деструктуризации объекта может потребоваться обернуть выражение в скобки. Объявление переменной в начале конструкции не обязательно. Можно использовать и существующие переменные. Однако при деструктуризации объекта может потребоваться обернуть выражение в скобки.
</li> </li>
<li>Сложные объекты и массивы тоже работают, деструктуризации можно вкладывать.</li> <li>Вложенные объекты и массивы тоже работают, деструктуризации можно вкладывать друг в друга, сохраняя ту же структуру, что и исходный объект/массив.</li>
</ul> </ul>
Как мы увидим далее, деструктуризации особенно пригодятся удобны при чтении объектных параметров функций. Как мы увидим далее, деструктуризации особенно пригодятся удобны при чтении объектных параметров функций.

View file

@ -76,10 +76,11 @@ showName("Юлий", "Цезарь", "Император", "Рима");
Оператор `…` собирает "все оставшиеся" аргументы, поэтому такое объявление не имеет смысла: Оператор `…` собирает "все оставшиеся" аргументы, поэтому такое объявление не имеет смысла:
```js ```js
function f(arg1, ...rest, arg2) { function f(arg1, ...rest, arg2) { // arg2 после ...rest ?!
// будет ошибка // будет ошибка
} }
``` ```
Параметр `...rest` должен быть в конце функции.
[/warn] [/warn]
@ -231,13 +232,15 @@ let inc = x => x+1;
let inc = function(x) { return x + 1; }; let inc = function(x) { return x + 1; };
``` ```
Если аргументов несколько, они оборачиваются в скобки, например: Если аргументов несколько, то они оборачиваются в скобки, например:
```js ```js
//+ run //+ run
'use strict'; 'use strict';
*!*
let sum = (a,b) => a + b; let sum = (a,b) => a + b;
*/!*
alert( sum(1, 2) ); // 3 alert( sum(1, 2) ); // 3
``` ```
@ -248,31 +251,33 @@ alert( sum(1, 2) ); // 3
//+ run //+ run
'use strict'; 'use strict';
let getTime = () => *!*
`${new Date().getHours()} : ${new Date().getMinutes()}`; let getTime = () => `${new Date().getHours()} : ${new Date().getMinutes()}`;
*/!*
alert( getTime() ); // текущее время alert( getTime() ); // текущее время
``` ```
Когда тело функции достаточно большое, то можно его обернуть в фигурные скобки `{…}`:
Когда тело функции достаточно большое, то можно его обернуть в `{…}`:
```js ```js
//+ run //+ run
'use strict'; 'use strict';
*!*
let getTime = () => { let getTime = () => {
let date = new Date(); let date = new Date();
let hours = date.getHours(); let hours = date.getHours();
let minutes = date.getMinutes(); let minutes = date.getMinutes();
return `${hours}:${minutes}`; return `${hours}:${minutes}`;
}; };
*/!*
alert( getTime() ); // текущее время alert( getTime() ); // текущее время
``` ```
Заметим, что как только тело функции оборачивается в `{…}`, то оно уже автоматически ничего не возвращает. Нужно делать явный `return`, как в примере выше. Заметим, что как только тело функции оборачивается в `{…}`, то оно уже автоматически перестаёт быть выражением. Такая функция должна делать явный `return`, как в примере выше, если конечно хочет что-либо возвратить.
Функции-стрелки очень удобны в качестве коллбеков, например: Функции-стрелки очень удобны в качестве коллбеков, например:
@ -282,14 +287,18 @@ alert( getTime() ); // текущее время
let arr = [5, 8, 3]; let arr = [5, 8, 3];
*!*
let sorted = arr.sort( (a,b) => a - b ); let sorted = arr.sort( (a,b) => a - b );
*/!*
alert(sorted); // 3, 5, 8 alert(sorted); // 3, 5, 8
``` ```
Такая запись -- коротка и понятна.
## Функции-стрелки не имеют своего this ## Функции-стрелки не имеют своего this
Внутри `this` -- тот же, что и снаружи. Внутри функций-стрелок -- тот же `this`, что и снаружи.
Это очень удобно в обработчиках событий и коллбэках, например: Это очень удобно в обработчиках событий и коллбэках, например:
@ -316,9 +325,9 @@ group.showList();
// Наш курс: Даша // Наш курс: Даша
``` ```
Здесь в `forEach` была использована функция-стрелка, поэтому `this.title` внутри -- это `group.title`. Здесь в `forEach` была использована функция-стрелка, поэтому `this.title` внутри -- тот же, что и во внешней функции `showList`. То есть, в данном случае -- `group.title`.
Если бы была обычная функция, то была бы ошибка: Если бы в `forEach` вместо функции-стрелки была обычная функция, то была бы ошибка:
```js ```js
//+ run //+ run
@ -340,9 +349,11 @@ let group = {
group.showList(); group.showList();
``` ```
При запуске будет "попытка прочитать свойство `title` у `undefined`", так как `.forEach(f)` при запуске `f` не ставит `this`. При запуске будет "попытка прочитать свойство `title` у `undefined`", так как `.forEach(f)` при запуске `f` не ставит `this`. То есть, `this` внутри `forEach` будет `undefined`.
...Вместе с тем, отсутствие у функции-стрелки "своего `this`" влечёт за собой естественное ограничение: такие функции нельзя использовать в качестве конструктора. [warn header="Функции стрелки нельзя запускать с `new`"]
Отсутствие у функции-стрелки "своего `this`" влечёт за собой естественное ограничение: такие функции нельзя использовать в качестве конструктора, то есть вызывать через `new`.
[/warn]
## Функции-стрелки не имеют своего arguments ## Функции-стрелки не имеют своего arguments
@ -362,7 +373,7 @@ function f() {
f(1); // 1 f(1); // 1
``` ```
Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` берутся из внешней функции. Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` всегда берутся из внешней "обычной" функции.
Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов. Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов.

View file

@ -146,6 +146,7 @@ function i18n(strings, ...values) {
return translated.replace(/\{(\d)\}/g, (s, num) => values[num]); return translated.replace(/\{(\d)\}/g, (s, num) => values[num]);
} }
// Пример использования
let name = "Вася"; let name = "Вася";
// Перевести строку // Перевести строку
@ -158,7 +159,7 @@ alert( i18n`Hello, ${name}!` ); // Привет, Вася!
Внутренняя кодировка строк в JavaScript -- это UTF-16, то есть под каждый символ отводится ровно два байта. Внутренняя кодировка строк в JavaScript -- это UTF-16, то есть под каждый символ отводится ровно два байта.
Но под всевозможные символы всех языков мира 2 байт не хватает. Поэтому бывает так, что одному символу языка соответствует два юникодных символа (итого 4 байта), такое сочетание называют "суррогатной парой". Но под всевозможные символы всех языков мира 2 байт не хватает. Поэтому бывает так, что одному символу языка соответствует два юникодных символа (итого 4 байта). Такое сочетание называют "суррогатной парой".
Самый частый пример суррогатной пары, который можно встретить в литературе -- это китайские иероглифы. Самый частый пример суррогатной пары, который можно встретить в литературе -- это китайские иероглифы.
@ -202,15 +203,19 @@ alert( '𝒳'.charCodeAt(0) + ' ' + '𝒳'.charCodeAt(1) ); // 55349 56499
alert( '𝒳'.codePointAt(0) ); // 119987 alert( '𝒳'.codePointAt(0) ); // 119987
``` ```
Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода": Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода", в отличие от старого `String.fromCharCode(code)`.
Например:
```js ```js
//+ run //+ run
// Правильно
alert( String.fromCodePoint(119987) ); // 𝒳 alert( String.fromCodePoint(119987) ); // 𝒳
// Неверно!
alert( String.fromCharCode(119987) ); // 풳 alert( String.fromCharCode(119987) ); // 풳
``` ```
Более старый метод `fromCharCode` взял первые два байта от числа `119987` и создал символ из них, а остальные отбросил, поэтому его результат неверен. Более старый метод `fromCharCode` в последней строке дал неверный результат, так как он берёт только первые два байта от числа `119987` и создаёт символ из них, а остальные отбрасывает.
### \u{длинный код} ### \u{длинный код}
@ -224,11 +229,13 @@ alert( String.fromCharCode(119987) ); // 풳
alert( "\u2033" ); // ″, символ двойного штриха alert( "\u2033" ); // ″, символ двойного штриха
``` ```
Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным. Вот так -- уже не то: Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным.
"Лишние" цифры уже не войдут в код, например:
```js ```js
//+ run //+ run
alert( "\u20331" ); // ″1, символ двойного штриха, а затем 1 alert( "\u20331" ); // Два символа: символ двойного штриха ″, а затем 1
``` ```
Чтобы вводить более длинные коды символов, добавили запись `\u{NNNNNNNN}`, где `NNNNNNNN` -- максимально восьмизначный (но можно и меньше цифр) код. Чтобы вводить более длинные коды символов, добавили запись `\u{NNNNNNNN}`, где `NNNNNNNN` -- максимально восьмизначный (но можно и меньше цифр) код.
@ -244,15 +251,15 @@ alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим
Во многих языках есть символы, которые получаются как сочетание основного символа и какого-то значка над ним или под ним. Во многих языках есть символы, которые получаются как сочетание основного символа и какого-то значка над ним или под ним.
Например, на основе обычного символа `a` существуют символы: `àáâäãåā`. Многие, но далеко не все такие сочетания имеют отдельный юникодный код. Например, на основе обычного символа `a` существуют символы: `àáâäãåā`. Самые часто встречающиеся подобные сочетания имеют отдельный юникодный код. Но отнюдь не все.
Для генерации произвольных сочетаний используются два юникодных символа: основа и значок. Для генерации произвольных сочетаний используются два юникодных символа: основа и значок.
Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то вместе будет `Ṡ`. Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то вместе будет "S с точкой сверху" `Ṡ`.
Если нужен ещё значок над той же буквой (или под ней) -- без проблем. Просто добавляем соответствующий символ. Если нужен ещё значок над той же буквой (или под ней) -- без проблем. Просто добавляем соответствующий символ.
К примеру, если добавить символ "точка снизу" (код `\u0323`), то будет `Ṩ` с двумя точками сверху и снизу. К примеру, если добавить символ "точка снизу" (код `\u0323`), то будет "S с двумя точками сверху и снизу" `Ṩ` .
В JavaScript-строке: В JavaScript-строке:
@ -261,7 +268,7 @@ alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим
alert("S\u0307\u0323"); // Ṩ alert("S\u0307\u0323"); // Ṩ
``` ```
Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, чтобы не хранить все возможные сочетания (которых громадное число), а с другой стороны -- возникает проблемка -- возможность представить одинаковый с точки зрения визуального отображения и интерпретации символ -- разными сочетаниями Unicode-кодов. Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, чтобы не хранить все возможные сочетания (которых громадное число), а с другой стороны -- возникает проблемка -- можно представить одинаковый с точки зрения визуального отображения и интерпретации символ -- разными сочетаниями Unicode-кодов.
```js ```js
//+ run //+ run
@ -271,7 +278,7 @@ alert("S\u0323\u0307"); // Ṩ
alert( "S\u0307\u0323" == "S\u0323\u0307" ); // false alert( "S\u0307\u0323" == "S\u0323\u0307" ); // false
``` ```
В первой строке сначала верхняя точка, а потом -- нижняя, во второй -- наоборот. По кодам строки не равны друг другу. В первой строке сначала верхняя точка, а потом -- нижняя, во второй -- наоборот. По кодам строки не равны друг другу. Но символ задают один и тот же.
С целью разрешить эту ситуацию, существует *юникодная нормализация*, при которой строки приводятся к единому, "нормальному", виду. С целью разрешить эту ситуацию, существует *юникодная нормализация*, при которой строки приводятся к единому, "нормальному", виду.
@ -293,9 +300,7 @@ alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
Это, конечно, не всегда так, просто в данном случае оказалось, что именно такой символ в юникоде уже есть. Если добавить значков, то нормализация уже даст несколько символов. Это, конечно, не всегда так, просто в данном случае оказалось, что именно такой символ в юникоде уже есть. Если добавить значков, то нормализация уже даст несколько символов.
Если хочется более подробно ознакомиться с вариантами и правилами нормализации -- они описаны в приложении к стандарту юникод [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/). Впрочем, для большинства практических задач информации, данной выше, должно быть вполне достаточно, но если хочется более подробно ознакомиться с вариантами и правилами нормализации -- они описаны в приложении к стандарту юникод [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/).
Впрочем, для большинства практических задач информации, данной выше, должно быть вполне достаточно.
## Полезные методы ## Полезные методы

View file

@ -5,14 +5,6 @@
По классам -- чуть позже, в отдельном разделе, оно того заслуживает. По классам -- чуть позже, в отдельном разделе, оно того заслуживает.
Для объектов есть очень приятные нововведения, которые касаются объявления свойств и методов. Новый синтаксис используется и в классах.
Нововведения в объектах и классах очень тесно взаимосвязаны, поэтому обе этих темы объединены в один раздел.
Мы начнём с объектов, а затем перейдём к классам.
## Короткое свойство ## Короткое свойство
Зачастую у нас есть переменные, например, `name` и `isAdmin`, и мы хотим использовать их в объекте. Зачастую у нас есть переменные, например, `name` и `isAdmin`, и мы хотим использовать их в объекте.
@ -120,12 +112,39 @@ Object.assign(user, visitor, admin);
alert( JSON.stringify(user) ); // user: Вася, visits: true, isAdmin: true alert( JSON.stringify(user) ); // user: Вася, visits: true, isAdmin: true
``` ```
## Object.is(value1, value2)
Возвращает `true`, если `value1 === value2`, иначе `false`.
Есть, однако, два отличия от обычного `===`, а именно:
```js
//+ run
// Сравнение +0 и -0
alert( Object.is(+0, -0)); // false
alert( +0 === -0 ); // true
// Сравнение с NaN
alert( Object.is(NaN, NaN) ); // true
alert( NaN === NaN ); // false
```
При сравнении объектов через `Object.is` успользуется алгоритм [SameValue](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevalue), который неявно применяется во многих других местах современного стандарта.
## Методы объекта ## Методы объекта
Долгое время в JavaScript термин "метод объекта" был просто альтернативным названием для свойства-функции. Долгое время в JavaScript термин "метод объекта" был просто альтернативным названием для свойства-функции.
Теперь это уже не так, добавлены именно "методы объекта". Они отличаются от обычных свойств-функций наличием специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Теперь это уже не так, добавлены именно "методы объекта". Они, по сути, являются "свойствами-функциями, привязанными к объекту".
Их особенности:
<ol>
<li>Более короткий синтаксис.</li>
<li>Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше, в разделе про `super`.</li>
</ol>
Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`. Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`.
@ -149,6 +168,8 @@ let user = {
user.sayHi(); // Вася user.sayHi(); // Вася
``` ```
Как видно, создание такого метода -- чуть короче, а обращение -- не отличается от обычной функции.
Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`: Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`:
```js ```js
@ -167,14 +188,30 @@ let user = {
alert( user.fullName ); // Вася Петров alert( user.fullName ); // Вася Петров
``` ```
Основное отличие "методов" от "просто функций" -- возможность обратиться к "родительскому методу" через ключевое слово `super`, о котором пойдёт речь дальше. Можно задать и метод с вычисляемым названием:
```js
//+ run
'use strict';
let methodName = "getFirstName";
let user = {
// в квадратных скобках может быть любое выражение,
// которое должно вернуть название метода
[methodName]() { // вместо [methodName]: function() {
return "Вася";
}
};
alert( user.getFirstName() ); // Вася
```
## super ## super
Ссылка `super` позволяет из метода обратиться к прототипу объекта. Вызов `super.parentProperty` позволяет из метода объекта получить свойство его прототипа.
Например, в коде ниже `super.walk` из `rabbit` обращается к `animal.walk`: Например, в коде ниже `rabbit` наследует от `animal`. Вызов `super.walk` из метода объекта `rabbit` обращается к `animal.walk`:
```js ```js
//+ run //+ run
@ -201,7 +238,7 @@ rabbit.walk();
При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов. При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов.
Например, тот же код, но со свойством-функцией `walk` вместо метода в `rabbit`: Например, если переписать этот код, оформив `rabbit.walk` как обычное свойство-функцию, то будет ошибка:
```js ```js
//+ run //+ run
@ -225,11 +262,9 @@ let rabbit = {
rabbit.walk(); rabbit.walk();
``` ```
Будет ошибка, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`. Ошибка возникнет, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`. В ней не работает `super`.
Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции. Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции. Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`:
Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`:
```js ```js
@ -270,7 +305,6 @@ let rabbit = {
__proto__: animal, __proto__: animal,
walk() { walk() {
super.walk(); super.walk();
alert(this);
} }
}; };
@ -278,12 +312,33 @@ let walk = rabbit.walk; // скопируем метод в переменную
*!* *!*
walk(); walk();
// I'm walking // I'm walking
// undefined
*/!* */!*
``` ```
В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`. В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`.
Это относится именно к `super`. Правила `this` для методов те же, в примере выше будет `undefined`. Это относится именно к `super`. Правила `this` для методов те же, что и для обычных функций. В примере выше при вызове `walk()` без объекта `this` будет `undefined`.
[/smart] [/smart]
## Итого
Улучшения в описании свойств:
<ul>
<li>Запись `name: name` можно заменить на просто `name`</li>
<li>Если имя свойства находится в переменной или задано выражением `expr`, то его можно указать в квадратных скобках `[expr]`.</li>
<li>Свойства-функции можно оформить как методы: `"prop: function() {"` -> `"prop() {"`.</li>
</ul>
В методах работает обращение к свойствам прототипа через `super.parentProperty`.
Для работы с прототипом:
<ul>
<li>`Object.setPrototypeOf(obj, proto)` -- метод для установки прототипа.</li>
<li>`obj.__proto__` -- ссылка на прототип.</li>
</ul>
Дополнительно:
<ul>
<li>Метод `Object.assign(target, src1, src2...)` -- копирует свойства из всех аргументов в первый объект.</li>
<li>Метод `Object.is(value1, value2)` проверяет два значения на равенство. В отличие от `===` считает `+0` и `-0` разными числами. А также считает, что `NaN` равно самому себе.</li>
</ul>

View file

@ -208,7 +208,7 @@ new Rabbit("Вася").walk();
// and jump! // and jump!
``` ```
[smart header="Обычные прототипы"] [smart header="Стандартная цепочка прототипов"]
При наследовании формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`: При наследовании формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`:
```js ```js
@ -226,9 +226,9 @@ alert( Rabbit.prototype.__proto__ == Animal.prototype ); // true
Немного особая история -- с конструктором. Немного особая история -- с конструктором.
Конструктор `constructor` родителя наследуется автоматически. То есть, если в потомке не указан свой `constructor`, то используется родительский. Конструктор `constructor` родителя наследуется автоматически. То есть, если в потомке не указан свой `constructor`, то используется родительский. В примере выше `Rabbit`, таким образом, использует `constructor` от `Animal`.
Если его переопределить, то родительский конструктор вызывается через `super()`, а не через `super.constructor()`. Если `constructor` переопределить, то чтобы в нём вызвать конструктор родителя -- используется синтаксис `super()` с аргументами для родителя.
Например, вызовем конструктор `Animal` в `Rabbit`: Например, вызовем конструктор `Animal` в `Rabbit`:
@ -250,7 +250,7 @@ class Rabbit extends Animal {
*!* *!*
constructor() { constructor() {
// вызвать конструктор Animal с аргументом "Кроль" // вызвать конструктор Animal с аргументом "Кроль"
super("Кроль"); // то же, что и Animal.apply(this, arguments) super("Кроль"); // то же, что и Animal.call(this, "Кроль")
} }
*/!* */!*
} }

View file

@ -82,6 +82,8 @@ alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный симво
## Использование символов ## Использование символов
Символы используются в качестве имён для методов и свойств объекта, которые являются системными, или которые необходимо скрыть.
Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации: Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации:
```js ```js
@ -102,11 +104,7 @@ for(let key in user) alert(key); // name, age
В примере выше выведутся все свойства, кроме символьного. В примере выше выведутся все свойства, кроме символьного.
Это нужно в первую очередь для различных системных свойств, которых в современном JavaScript очень много. В современно JavaScript есть много системных символов. Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1). В спецификации принято символы для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`.
Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1).
В спецификации принято для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`.
Например: Например:
<ul> <ul>
@ -115,11 +113,15 @@ for(let key in user) alert(key); // name, age
<li>...и т.п.</li> <li>...и т.п.</li>
</ul> </ul>
Смысл здесь в том, что допустим надо добавить к объекту "особый" функционал, например преобразование к примитиву или функцию итерации, или ещё что-то... Мы легко поймём смысл введения нового типа "символ", если поставим себя на место создателей языка JavaScript.
Новый стандарт не мог просто сказать, что "свойство obj.toPrimitive теперь системное, оно делает то-то и то-то". Ведь свойство с таким именем, вполне возможно, используется в существующем коде. И он сломался бы. Допустим, надо добавить к объекту "особый" функционал, например, функцию, которая задаёт преобразование объекта к примитиву.
Поэтому ввели целый тип "символы", которые можно использовать для задания свойств, которые уникальны, не участвуют в итерации и заведомо не конфликтуют со старым кодом. Можно, конечно, сказать, что "свойство obj.toPrimitive теперь системное, оно делает то-то и то-то". Но это опасно. Мало ли, вполне возможно, что свойство с таким именем уже используется в существующем коде. И если сделать его системным, то он сломается.
Нельзя просто взять и зарезервировать какие-то свойства существующих объектов для нового функционала.
Поэтому ввели целый тип "символы". Их можно использовать для задания свойств, которые уникальны, не участвуют в итерации и заведомо не конфликтуют со старым кодом.
Например: Например:
```js ```js
@ -131,10 +133,13 @@ let obj = {
[Symbol.iterator]: function() {} [Symbol.iterator]: function() {}
} }
alert(obj.iterator); // 1, символ не конфликтует alert(obj.iterator); // 1
alert(obj[Symbol.iterator]) // function, символ не конфликтует
``` ```
Чтобы получить символы, есть особый вызов [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols). Выше мы использовали системный символ `Symbol.iterator`, поскольку он один из самых широко поддерживаемых. Мы подробно разберём его смысл в следующих главах, пока же -- это просто пример символа.
Чтобы получить все символы объекта, есть особый вызов [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols).
Эта функция возвращает все символы в объекте. Заметим, что `getOwnPropertyNames` символы не возвращает. Эта функция возвращает все символы в объекте. Заметим, что `getOwnPropertyNames` символы не возвращает.
@ -149,21 +154,20 @@ let obj = {
// один символ в объекте // один символ в объекте
alert( Object.getOwnPropertySymbols(obj) ); // Symbol(Symbol.iterator) alert( Object.getOwnPropertySymbols(obj) ); // Symbol(Symbol.iterator)
// и одно обычное свойство
alert( Object.getOwnPropertyNames(obj) ); // iterator
``` ```
Один из самых известных и полезных символов -- это `Symbol.iterator`. Мы будем активно его использовать позже, в главе про итераторы.
## Итого ## Итого
<ul> <ul>
<li>Символы -- новый примитивный тип, предназначенный для уникальных идентификаторов.</li> <li>Символы -- новый примитивный тип, предназначенный для уникальных идентификаторов.</li>
<li>Все символы уникальны, символы с одинаковым именем не равны друг другу.</li> <li>Все символы уникальны, символы с одинаковым именем не равны друг другу.</li>
<li>Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`.</li> <li>Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`.</li>
<li>Основная область использования символов -- это системные свойства объектов. Поддержка у них пока небольшая, но она растёт. Символы позволяют добавлять в стандарт новые "особые" свойства объектов, при этом не резервируя соответствующие названия.</li>
</ul> </ul>
Основная область использования символов -- это системные свойства объектов. Поддержка у них пока небольшая, но она растёт. Системные символы позволяют добавлять в стандарт новые "особые" свойства объектов, при этом не резервируя соответствующие названия.
Но, конечно, мы можем создавать и свои локальные и глобальные символы, использовать их в своих объектах.