diff --git a/1-js/10-es-modern/10-set-map/article.md b/1-js/10-es-modern/10-set-map/article.md index f1973f2a..ae6ed07e 100644 --- a/1-js/10-es-modern/10-set-map/article.md +++ b/1-js/10-es-modern/10-set-map/article.md @@ -231,6 +231,7 @@ set.forEach((value, valueAgain, set) => { Если поместить такие данные в `WeakMap`, а объект сделать ключом, то они будут автоматически удалены из памяти, когда удалится элемент. Например: + ```js // текущие активные пользователи let activeUsers = [ @@ -248,7 +249,7 @@ weakMap[activeUsers[0]] = 1; weakMap[activeUsers[1]] = 2; weakMap[activeUsers[2]] = 3; -alert( weakMap[activeUsers[0]].name ); // Вася +alert( weakMap[activeUsers[0]] ); // 1 activeUsers.splice(0, 1); // Вася более не активный пользователь @@ -259,8 +260,30 @@ activeUsers.splice(0, 1); // Петя более не активный поль // weakMap теперь содержит только 1 элемент ``` -TODO WRITE MORE - +У WeakMap есть ряд ограничений: + + +Иными словами, `WeakMap` работает только на запись (`set`, `delete`) и чтение (`get`, `has`) элементов по конкретному ключу, а не как полноценная коллекция. Нельзя вывести всё содержимое `WeakMap`, нет соответствующих методов. + +Это связано с тем, что содержимое `WeakMap` может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. Нет какого-то конкретного момента, когда такая очистка точно произойдёт -- это определяется внутренними алгоритмами сборщика и его сведениями о системе. + +Поэтому содержимое `WeakMap` в произвольный момент, строго говоря, не определено. Может быть, сборщик мусора уже удалил какие-то записи, а может и нет. С этим, а также с требованиями к эффективной реализации `WeakMap`, и связано отсутствие методов, осуществляющих доступ ко всем записям. + +То же самое относится и к `WeakSet`: можно добавлять элементы, проверять их наличие, но нельзя получить их список и даже узнать количество. + +Эти ограничения могут показаться неудобными, но по сути они не мешают `WeakMap/WeakSet` выполнять свою основную задачу -- быть "вторичным" хранилищем данных для объектов, актуальный список которых (и сами они) хранятся в каком-то другом месте. + +## Итого + + diff --git a/1-js/10-es-modern/3-destructuring/article.md b/1-js/10-es-modern/3-destructuring/article.md index a4d64cef..d5ea49c9 100644 --- a/1-js/10-es-modern/3-destructuring/article.md +++ b/1-js/10-es-modern/3-destructuring/article.md @@ -24,7 +24,9 @@ alert(lastName); // Кантор //+ run 'use strict'; +*!* let [firstName, lastName, ...rest] = "Юлий Цезарь Император Рима".split(" "); +*/!* alert(firstName); // Юлий alert(lastName); // Цезарь @@ -33,6 +35,59 @@ alert(rest); // Император,Рима (массив из 2х элем Значением `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 //+ run @@ -79,7 +134,7 @@ alert(`${title} ${w} ${h}`); // Меню 100 200 В примере выше свойство `width` отправилось в переменную `w`, свойство `height` -- в переменную `h`, а `title` -- в переменную с тем же названием. -Если каких-то свойств в объекте нет,можно указать значение по умолчанию через знак равенства `=`, вот так; +Если каких-то свойств в объекте нет, можно указать значение по умолчанию через знак равенства `=`, вот так; ```js //+ run @@ -116,7 +171,7 @@ alert(`${title} ${w} ${h}`); // Меню 100 200 А что, если в объекте больше значений, чем переменных? Можно ли куда-то присвоить "остаток"? -Такая возможность планируется в будущем стандарте ES-2016, и выглядеть она будет так: +Такой возможности в текущем стандарте нет. Она планируется в будущем стандарте, и выглядеть она будет аналогично массивам: ```js //+ run @@ -136,9 +191,7 @@ let {title, ...size} = menuOptions; // size = { width: 100, height: 200} (остаток) ``` -Ещё раз заметим, что аналогичный массивам оператор `...` ("spread") для объектов пока не стандарт. - -Этот код будет работать, например, при использовании Babel со включёнными экспериментальными возможностями. +Этот код будет работать, например, при использовании Babel со включёнными экспериментальными возможностями, но ещё раз заметим, что в текущий стандарт такая возможность не вошла. [smart header="Деструктуризация без объявления"] @@ -208,14 +261,14 @@ alert(`${title} ${width} ${height} ${item1} ${item2}`);
  • Синтаксис: ```js let {prop : varName = default, ...} = object -let [var1, var2, ...rest] = array +let [var1=default, var2, ...rest] = array ``` -Здесь двоеточие `:` задаёт отображение свойства в переменную, а `=` задаёт значение по умолчанию (если нужно). +Здесь двоеточие `:` задаёт отображение свойства в переменную, а `=` задаёт выражение, которое будет использовано, если значение отсутствует (не указано или `undefined`). -Объявление переменной вначале не обязательно, если переменные уже есть, но без него при деструктуризации объекта может потребоваться обернуть выражение в скобки. +Объявление переменной в начале конструкции не обязательно. Можно использовать и существующие переменные. Однако при деструктуризации объекта может потребоваться обернуть выражение в скобки.
  • -
  • Сложные объекты и массивы тоже работают, деструктуризации можно вкладывать.
  • +
  • Вложенные объекты и массивы тоже работают, деструктуризации можно вкладывать друг в друга, сохраняя ту же структуру, что и исходный объект/массив.
  • Как мы увидим далее, деструктуризации особенно пригодятся удобны при чтении объектных параметров функций. diff --git a/1-js/10-es-modern/4-es-function/article.md b/1-js/10-es-modern/4-es-function/article.md index 4d05bb2e..7786262a 100644 --- a/1-js/10-es-modern/4-es-function/article.md +++ b/1-js/10-es-modern/4-es-function/article.md @@ -76,10 +76,11 @@ showName("Юлий", "Цезарь", "Император", "Рима"); Оператор `…` собирает "все оставшиеся" аргументы, поэтому такое объявление не имеет смысла: ```js -function f(arg1, ...rest, arg2) { +function f(arg1, ...rest, arg2) { // arg2 после ...rest ?! // будет ошибка } ``` +Параметр `...rest` должен быть в конце функции. [/warn] @@ -231,13 +232,15 @@ let inc = x => x+1; let inc = function(x) { return x + 1; }; ``` -Если аргументов несколько, они оборачиваются в скобки, например: +Если аргументов несколько, то они оборачиваются в скобки, например: ```js //+ run 'use strict'; +*!* let sum = (a,b) => a + b; +*/!* alert( sum(1, 2) ); // 3 ``` @@ -248,31 +251,33 @@ alert( sum(1, 2) ); // 3 //+ run 'use strict'; -let getTime = () => - `${new Date().getHours()} : ${new Date().getMinutes()}`; +*!* +let getTime = () => `${new Date().getHours()} : ${new Date().getMinutes()}`; +*/!* alert( getTime() ); // текущее время ``` - -Когда тело функции достаточно большое, то можно его обернуть в `{…}`: +Когда тело функции достаточно большое, то можно его обернуть в фигурные скобки `{…}`: ```js //+ run 'use strict'; +*!* let getTime = () => { let date = new Date(); let hours = date.getHours(); let minutes = date.getMinutes(); return `${hours}:${minutes}`; }; +*/!* alert( getTime() ); // текущее время ``` -Заметим, что как только тело функции оборачивается в `{…}`, то оно уже автоматически ничего не возвращает. Нужно делать явный `return`, как в примере выше. +Заметим, что как только тело функции оборачивается в `{…}`, то оно уже автоматически перестаёт быть выражением. Такая функция должна делать явный `return`, как в примере выше, если конечно хочет что-либо возвратить. Функции-стрелки очень удобны в качестве коллбеков, например: @@ -282,14 +287,18 @@ alert( getTime() ); // текущее время let arr = [5, 8, 3]; +*!* let sorted = arr.sort( (a,b) => a - b ); +*/!* alert(sorted); // 3, 5, 8 ``` +Такая запись -- коротка и понятна. + ## Функции-стрелки не имеют своего this -Внутри `this` -- тот же, что и снаружи. +Внутри функций-стрелок -- тот же `this`, что и снаружи. Это очень удобно в обработчиках событий и коллбэках, например: @@ -316,9 +325,9 @@ group.showList(); // Наш курс: Даша ``` -Здесь в `forEach` была использована функция-стрелка, поэтому `this.title` внутри -- это `group.title`. +Здесь в `forEach` была использована функция-стрелка, поэтому `this.title` внутри -- тот же, что и во внешней функции `showList`. То есть, в данном случае -- `group.title`. -Если бы была обычная функция, то была бы ошибка: +Если бы в `forEach` вместо функции-стрелки была обычная функция, то была бы ошибка: ```js //+ run @@ -340,9 +349,11 @@ let group = { 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 @@ -362,7 +373,7 @@ function f() { f(1); // 1 ``` -Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` берутся из внешней функции. +Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` всегда берутся из внешней "обычной" функции. Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов. diff --git a/1-js/10-es-modern/5-es-string/article.md b/1-js/10-es-modern/5-es-string/article.md index fcf8430c..92c18d0c 100644 --- a/1-js/10-es-modern/5-es-string/article.md +++ b/1-js/10-es-modern/5-es-string/article.md @@ -146,6 +146,7 @@ function i18n(strings, ...values) { return translated.replace(/\{(\d)\}/g, (s, num) => values[num]); } +// Пример использования let name = "Вася"; // Перевести строку @@ -158,7 +159,7 @@ alert( i18n`Hello, ${name}!` ); // Привет, Вася! Внутренняя кодировка строк в JavaScript -- это UTF-16, то есть под каждый символ отводится ровно два байта. -Но под всевозможные символы всех языков мира 2 байт не хватает. Поэтому бывает так, что одному символу языка соответствует два юникодных символа (итого 4 байта), такое сочетание называют "суррогатной парой". +Но под всевозможные символы всех языков мира 2 байт не хватает. Поэтому бывает так, что одному символу языка соответствует два юникодных символа (итого 4 байта). Такое сочетание называют "суррогатной парой". Самый частый пример суррогатной пары, который можно встретить в литературе -- это китайские иероглифы. @@ -202,15 +203,19 @@ alert( '𝒳'.charCodeAt(0) + ' ' + '𝒳'.charCodeAt(1) ); // 55349 56499 alert( '𝒳'.codePointAt(0) ); // 119987 ``` -Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода": +Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода", в отличие от старого `String.fromCharCode(code)`. + +Например: ```js //+ run +// Правильно alert( String.fromCodePoint(119987) ); // 𝒳 +// Неверно! alert( String.fromCharCode(119987) ); // 풳 ``` -Более старый метод `fromCharCode` взял первые два байта от числа `119987` и создал символ из них, а остальные отбросил, поэтому его результат неверен. +Более старый метод `fromCharCode` в последней строке дал неверный результат, так как он берёт только первые два байта от числа `119987` и создаёт символ из них, а остальные отбрасывает. ### \u{длинный код} @@ -224,11 +229,13 @@ alert( String.fromCharCode(119987) ); // 풳 alert( "\u2033" ); // ″, символ двойного штриха ``` -Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным. Вот так -- уже не то: +Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным. + +"Лишние" цифры уже не войдут в код, например: ```js //+ run -alert( "\u20331" ); // ″1, символ двойного штриха, а затем 1 +alert( "\u20331" ); // Два символа: символ двойного штриха ″, а затем 1 ``` Чтобы вводить более длинные коды символов, добавили запись `\u{NNNNNNNN}`, где `NNNNNNNN` -- максимально восьмизначный (но можно и меньше цифр) код. @@ -244,15 +251,15 @@ alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим Во многих языках есть символы, которые получаются как сочетание основного символа и какого-то значка над ним или под ним. -Например, на основе обычного символа `a` существуют символы: `àáâäãåā`. Многие, но далеко не все такие сочетания имеют отдельный юникодный код. +Например, на основе обычного символа `a` существуют символы: `àáâäãåā`. Самые часто встречающиеся подобные сочетания имеют отдельный юникодный код. Но отнюдь не все. Для генерации произвольных сочетаний используются два юникодных символа: основа и значок. -Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то вместе будет `Ṡ`. +Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то вместе будет "S с точкой сверху" `Ṡ`. Если нужен ещё значок над той же буквой (или под ней) -- без проблем. Просто добавляем соответствующий символ. -К примеру, если добавить символ "точка снизу" (код `\u0323`), то будет `Ṩ` с двумя точками сверху и снизу. +К примеру, если добавить символ "точка снизу" (код `\u0323`), то будет "S с двумя точками сверху и снизу" `Ṩ` . В JavaScript-строке: @@ -261,7 +268,7 @@ alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим alert("S\u0307\u0323"); // Ṩ ``` -Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, чтобы не хранить все возможные сочетания (которых громадное число), а с другой стороны -- возникает проблемка -- возможность представить одинаковый с точки зрения визуального отображения и интерпретации символ -- разными сочетаниями Unicode-кодов. +Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, чтобы не хранить все возможные сочетания (которых громадное число), а с другой стороны -- возникает проблемка -- можно представить одинаковый с точки зрения визуального отображения и интерпретации символ -- разными сочетаниями Unicode-кодов. ```js //+ run @@ -271,7 +278,7 @@ alert("S\u0323\u0307"); // Ṩ 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/). ## Полезные методы diff --git a/1-js/10-es-modern/6-es-objects/article.md b/1-js/10-es-modern/6-es-objects/article.md index 1b4c4a71..40772e76 100644 --- a/1-js/10-es-modern/6-es-objects/article.md +++ b/1-js/10-es-modern/6-es-objects/article.md @@ -5,14 +5,6 @@ По классам -- чуть позже, в отдельном разделе, оно того заслуживает. - -Для объектов есть очень приятные нововведения, которые касаются объявления свойств и методов. Новый синтаксис используется и в классах. - -Нововведения в объектах и классах очень тесно взаимосвязаны, поэтому обе этих темы объединены в один раздел. - -Мы начнём с объектов, а затем перейдём к классам. - - ## Короткое свойство Зачастую у нас есть переменные, например, `name` и `isAdmin`, и мы хотим использовать их в объекте. @@ -120,12 +112,39 @@ Object.assign(user, visitor, admin); 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 термин "метод объекта" был просто альтернативным названием для свойства-функции. -Теперь это уже не так, добавлены именно "методы объекта". Они отличаются от обычных свойств-функций наличием специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. +Теперь это уже не так, добавлены именно "методы объекта". Они, по сути, являются "свойствами-функциями, привязанными к объекту". + +Их особенности: + +
      +
    1. Более короткий синтаксис.
    2. +
    3. Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше, в разделе про `super`.
    4. +
    Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`. @@ -149,6 +168,8 @@ let user = { user.sayHi(); // Вася ``` +Как видно, создание такого метода -- чуть короче, а обращение -- не отличается от обычной функции. + Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`: ```js @@ -167,14 +188,30 @@ let user = { alert( user.fullName ); // Вася Петров ``` -Основное отличие "методов" от "просто функций" -- возможность обратиться к "родительскому методу" через ключевое слово `super`, о котором пойдёт речь дальше. +Можно задать и метод с вычисляемым названием: +```js +//+ run +'use strict'; + +let methodName = "getFirstName"; + +let user = { + // в квадратных скобках может быть любое выражение, + // которое должно вернуть название метода + [methodName]() { // вместо [methodName]: function() { + return "Вася"; + } +}; + +alert( user.getFirstName() ); // Вася +``` ## super -Ссылка `super` позволяет из метода обратиться к прототипу объекта. +Вызов `super.parentProperty` позволяет из метода объекта получить свойство его прототипа. -Например, в коде ниже `super.walk` из `rabbit` обращается к `animal.walk`: +Например, в коде ниже `rabbit` наследует от `animal`. Вызов `super.walk` из метода объекта `rabbit` обращается к `animal.walk`: ```js //+ run @@ -201,7 +238,7 @@ rabbit.walk(); При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов. -Например, тот же код, но со свойством-функцией `walk` вместо метода в `rabbit`: +Например, если переписать этот код, оформив `rabbit.walk` как обычное свойство-функцию, то будет ошибка: ```js //+ run @@ -225,11 +262,9 @@ let rabbit = { rabbit.walk(); ``` -Будет ошибка, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`. +Ошибка возникнет, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`. В ней не работает `super`. -Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции. - -Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`: +Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции. Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`: ```js @@ -270,7 +305,6 @@ let rabbit = { __proto__: animal, walk() { super.walk(); - alert(this); } }; @@ -278,12 +312,33 @@ let walk = rabbit.walk; // скопируем метод в переменную *!* walk(); // I'm walking -// undefined */!* ``` В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`. -Это относится именно к `super`. Правила `this` для методов те же, в примере выше будет `undefined`. +Это относится именно к `super`. Правила `this` для методов те же, что и для обычных функций. В примере выше при вызове `walk()` без объекта `this` будет `undefined`. [/smart] +## Итого + +Улучшения в описании свойств: + + +В методах работает обращение к свойствам прототипа через `super.parentProperty`. + +Для работы с прототипом: + + +Дополнительно: + diff --git a/1-js/10-es-modern/7-es-classes/article.md b/1-js/10-es-modern/7-es-classes/article.md index 2d4fe3ce..1ba08f25 100644 --- a/1-js/10-es-modern/7-es-classes/article.md +++ b/1-js/10-es-modern/7-es-classes/article.md @@ -208,7 +208,7 @@ new Rabbit("Вася").walk(); // and jump! ``` -[smart header="Обычные прототипы"] +[smart header="Стандартная цепочка прототипов"] При наследовании формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`: ```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`: @@ -250,7 +250,7 @@ class Rabbit extends Animal { *!* constructor() { // вызвать конструктор Animal с аргументом "Кроль" - super("Кроль"); // то же, что и Animal.apply(this, arguments) + super("Кроль"); // то же, что и Animal.call(this, "Кроль") } */!* } diff --git a/1-js/10-es-modern/8-symbol/article.md b/1-js/10-es-modern/8-symbol/article.md index 4fb61419..86260c05 100644 --- a/1-js/10-es-modern/8-symbol/article.md +++ b/1-js/10-es-modern/8-symbol/article.md @@ -82,6 +82,8 @@ alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный симво ## Использование символов +Символы используются в качестве имён для методов и свойств объекта, которые являются системными, или которые необходимо скрыть. + Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации: ```js @@ -102,11 +104,7 @@ 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`. +В современно JavaScript есть много системных символов. Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1). В спецификации принято символы для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`. Например: -Смысл здесь в том, что допустим надо добавить к объекту "особый" функционал, например преобразование к примитиву или функцию итерации, или ещё что-то... +Мы легко поймём смысл введения нового типа "символ", если поставим себя на место создателей языка JavaScript. -Новый стандарт не мог просто сказать, что "свойство obj.toPrimitive теперь системное, оно делает то-то и то-то". Ведь свойство с таким именем, вполне возможно, используется в существующем коде. И он сломался бы. +Допустим, надо добавить к объекту "особый" функционал, например, функцию, которая задаёт преобразование объекта к примитиву. -Поэтому ввели целый тип "символы", которые можно использовать для задания свойств, которые уникальны, не участвуют в итерации и заведомо не конфликтуют со старым кодом. +Можно, конечно, сказать, что "свойство obj.toPrimitive теперь системное, оно делает то-то и то-то". Но это опасно. Мало ли, вполне возможно, что свойство с таким именем уже используется в существующем коде. И если сделать его системным, то он сломается. + +Нельзя просто взять и зарезервировать какие-то свойства существующих объектов для нового функционала. + +Поэтому ввели целый тип "символы". Их можно использовать для задания свойств, которые уникальны, не участвуют в итерации и заведомо не конфликтуют со старым кодом. Например: ```js @@ -131,10 +133,13 @@ let obj = { [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` символы не возвращает. @@ -149,21 +154,20 @@ let obj = { // один символ в объекте alert( Object.getOwnPropertySymbols(obj) ); // Symbol(Symbol.iterator) + +// и одно обычное свойство +alert( Object.getOwnPropertyNames(obj) ); // iterator ``` - - -Один из самых известных и полезных символов -- это `Symbol.iterator`. Мы будем активно его использовать позже, в главе про итераторы. - ## Итого +Основная область использования символов -- это системные свойства объектов. Поддержка у них пока небольшая, но она растёт. Системные символы позволяют добавлять в стандарт новые "особые" свойства объектов, при этом не резервируя соответствующие названия. - +Но, конечно, мы можем создавать и свои локальные и глобальные символы, использовать их в своих объектах.