es6
156
1-js/10-es-modern/10-set-map/article.md
Normal file
|
@ -0,0 +1,156 @@
|
|||
|
||||
# Set, Map, WeakSet и WeakMap
|
||||
|
||||
Новые типы коллекций в JavaScript: `Set`, `Map`, `WeakSet` и `WeakMap`.
|
||||
|
||||
|
||||
## Map
|
||||
|
||||
`Map` -- коллекция для хранения записей вида `ключ: значение`.
|
||||
|
||||
В отличие от объектов, в которых ключами могут быть только строки, в `Map` ключом может быть произвольное значение, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let map = new Map();
|
||||
|
||||
map.set('1', 'str1'); // строка
|
||||
map
|
||||
.set(1, 'num1') // число
|
||||
.set(true, 'bool1'); // булевое
|
||||
|
||||
// в обычном объекте это было бы одно и то же
|
||||
alert( map.get(1) ); // 'num1'
|
||||
alert( map.get('1') ); // 'str1'
|
||||
```
|
||||
|
||||
Как видно из примера выше, для сохранения и чтения значений используются методы `get` и `set`, причём `set` можно чейнить.
|
||||
|
||||
**При создании `Map` можно сразу инициализовать списком значений.**
|
||||
|
||||
Объект `map` с тремя ключами, как и в примере выше:
|
||||
|
||||
```js
|
||||
let map = new Map([
|
||||
['1', 'str1'],
|
||||
[1, 'num1'],
|
||||
[true, 'bool1']
|
||||
]);
|
||||
```
|
||||
|
||||
Аргументом `new Map` должен быть итерируемый объект (не обязательно именно массив), которые должен возвратить объект с ключами `0`,`1` -- также не обязательно массив. Везде утиная типизация, максимальная гибкость.
|
||||
|
||||
**В качестве ключей можно использовать и объекты:**
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let user = { name: "Вася" };
|
||||
|
||||
let visitsCountMap = new Map();
|
||||
|
||||
*!*
|
||||
// объект user является ключом в visitsCountMap
|
||||
visitsCountMap.set(user, 123);
|
||||
*/!*
|
||||
|
||||
alert( visitsCountMap.get(user) ); // 123
|
||||
```
|
||||
|
||||
[smart header="Как map сравнивает ключи"]
|
||||
Для проверки значений на эквивалентность используется алгоритм [SameValueZero](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevaluezero). Он аналогичен строгому равенству `===`, отличие -- в том, что `NaN` считается равным `NaN`.
|
||||
[/smart]
|
||||
|
||||
Для удаления записей используется метод:
|
||||
<ul>
|
||||
<li>`map.delete(key)` -- возвращает `true`, если ключ существовал, иначе `false`.</li>
|
||||
</ul>
|
||||
|
||||
Для проверки существования ключа:
|
||||
|
||||
<ul>
|
||||
<li>`map.has(key)` -- возвращает `true`, если ключ есть, иначе `false`.</li>
|
||||
</ul>
|
||||
|
||||
Ещё раз заметим, что используемый алгоритм сравнения ключей аналогичен `===`, за исключением `NaN`, которое равно самому себе:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let map = new Map([ [NaN: 1] ]);
|
||||
|
||||
alert( map.has(NaN) ); // true
|
||||
alert( map.get(NaN) ); // 1
|
||||
alert( map.delete(NaN) ); // true
|
||||
```
|
||||
|
||||
### Итерация
|
||||
|
||||
Для итерации используется один из трёх методов:
|
||||
<ul>
|
||||
<li>`map.keys()` -- возвращает итерируемый объект для ключей,</li>
|
||||
<li>`map.values()` -- возвращает итерируемый объект для значений,</li>
|
||||
<li>`map.entries()` -- возвращает итерируемый объект для записей `[ключ, значение]`, он используется по умолчанию в `for..of`.</li>
|
||||
</ul>
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let recipeMap = new Map([
|
||||
['огурцов', '500 гр'],
|
||||
['помидоров', '350 гр'],
|
||||
['сметаны', '50 гр']
|
||||
]);
|
||||
|
||||
for(let fruit of recipeMap.keys()) {
|
||||
alert(fruit); // огурцов, помидоров, сметаны
|
||||
}
|
||||
|
||||
for(let amount of recipeMap.values()) {
|
||||
alert(amount); // 500 гр, 350 гр, 50 гр
|
||||
}
|
||||
|
||||
for(let entry of recipeMap) { // то же что и recipeMap.entries()
|
||||
alert(entry); // огурцов,500 гр , и т.д., массивы по 2 значения
|
||||
}
|
||||
```
|
||||
|
||||
Кроме того, у `Map` есть стандартный методы `forEach`, аналогичный массиву:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let recipeMap = new Map([
|
||||
['огурцов', '500 гр'],
|
||||
['помидоров', '350 гр'],
|
||||
['сметаны', '50 гр']
|
||||
]);
|
||||
|
||||
recipeMap.forEach( (value, key, map) => {
|
||||
alert(`${key}: ${value}`); // огурцов: 500 гр, и т.д.
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Есть и другие методы:
|
||||
|
||||
<ul>
|
||||
<li>`map.size()` -- количество записей в
|
||||
|
||||
|
||||
ToDo
|
||||
|
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 |
|
@ -289,37 +289,18 @@ new Rabbit();
|
|||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Классы можно объявлять как в основном потоке кода, так и "инлайн", по аналогии с Function Declaration и Expression.</li>
|
||||
<li>В объявлении классов можно использовать методы, геттеры/сеттеры и вычислимые названия методов.</li>
|
||||
<li>При наследовании вызов конструктора родителя осуществлятся через `super(...args)`, вызов родительских методов -- через `super.method(...args)`.</li>
|
||||
</ul>
|
||||
|
||||
Концепция классов, которая после долгих обсуждений получилась в стандарте EcmaScript, носит название "максимально минимальной". То есть, в неё вошли только те возможности, которые уж точно необходимы.
|
||||
|
||||
В частности, не вошли "приватные" и "защищённые" свойства. То есть, все свойства и методы класса технически доступны снаружи. Возможно, они появятся в будущих редакциях стандарта.
|
||||
|
||||
|
||||
В классах нет возможности ограничивать
|
||||
|
||||
|
||||
|
217
1-js/10-es-modern/9-iterators/article.md
Normal file
|
@ -0,0 +1,217 @@
|
|||
|
||||
# Итераторы
|
||||
|
||||
В современный JavaScript добавлена новая концепция "итерируемых" (iterable) объектов.
|
||||
|
||||
Итерируемые или, иными словами, "перебираемые" объекты -- это те, содержимое которых можно перебрать в цикле.
|
||||
|
||||
Например, массив, но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов), а итераторы дают возможность сделать "перебираемыми" любые объекты.
|
||||
|
||||
Для перебора таких объектов добавлен новый синтаксис цикла: `for..of`.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let arr = [1, 2, 3]; // массив — пример итерируемого объекта
|
||||
|
||||
for(let value of arr) {
|
||||
alert(value); // 1, затем 2, затем 3
|
||||
}
|
||||
```
|
||||
|
||||
Также итерируемой является строка:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
for(let char of "Привет") {
|
||||
alert(char); // Выведет по одной букве: П, р, и, в, е, т
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Итераторы -- расширяющая понятие "массив" концепция, которая пронизывает современный стандарт JavaScript сверху донизу.
|
||||
|
||||
Практически везде, где нужен перебор, он осуществляется через итераторы. Это включает в себя не только строки, массивы, но и вызов функции с оператором spread `f(...args)`, и многое другое.
|
||||
|
||||
## Свой итератор
|
||||
|
||||
Допустим, у нас есть некий объект, который надо "умным способом" перебрать.
|
||||
|
||||
Например, `range` -- диапазон чисел от `range.from` до `range.to`:
|
||||
|
||||
```js
|
||||
let range = {
|
||||
from: 1,
|
||||
to: 5
|
||||
};
|
||||
```
|
||||
|
||||
Для возможности использовать объект в `for..of` ему ставится свойство с названием `Symbol.iterator`. `Symbol.iterator` -- системный символ, который вызывает `for..of` в начале выполнения.
|
||||
|
||||
При вызове метода `Symbol.iterator` перебираемый (итерируемый) объект должен возвращать другой объект ("итератор"), который умеет осуществлять перебор.
|
||||
|
||||
По стандарту у такого объекта должен быть метод `next()`, который при каждом вызове возвращает очередное значение и окончен ли перебор.
|
||||
|
||||
Так это выглядит в коде:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let range = {
|
||||
from: 1,
|
||||
to: 5
|
||||
}
|
||||
|
||||
// сделаем объект range итерируемым
|
||||
range[Symbol.iterator] = function() {
|
||||
|
||||
let current = this.from;
|
||||
let last = this.to;
|
||||
|
||||
// метод должен вернуть объект с next()
|
||||
return {
|
||||
next() {
|
||||
if (current <= last) {
|
||||
return {
|
||||
done: false,
|
||||
value: current++
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
done: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
for (let num of range) {
|
||||
alert(num); // 1, затем 2, 3, 4, 5
|
||||
}
|
||||
```
|
||||
|
||||
...То есть, здесь имеет место разделение сущностей:
|
||||
|
||||
<ul>
|
||||
<li>Перебираемый объект сам не реализует методы для своего перебора. Для этого существует другой объект, который хранит текущее состояние перебора и возвращает значение.</li>
|
||||
<li>Этот объект называется итератором и создаётся при вызове метода `Symbol.iterator`.</li>
|
||||
<li>У итератора должен быть метод `next()`, который при каждом вызове возвращает объект со свойствами:
|
||||
<ul>
|
||||
<li>`value` -- очередное значение,
|
||||
<li>`done` -- равно `false`, если есть ещё значения, и `true` -- в конце.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Конструкция `for..of` получает итератор и вызывает метод `next()` прозрачно и автоматически до получения `done: true`.
|
||||
|
||||
Впрочем, хоть такое отделение функционала перебора от самого объекта даёт дополнительную гибкость, например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток, зачастую оно не нужно.
|
||||
|
||||
Чтобы функционал по перебору заключить в самом объекте, можно вернуть `this` в качестве итератора:
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let range = {
|
||||
from: 1,
|
||||
to: 5,
|
||||
|
||||
*!*
|
||||
[Symbol.iterator]() {
|
||||
return this;
|
||||
},
|
||||
*/!*
|
||||
|
||||
next() {
|
||||
if (this.current === undefined) {
|
||||
// инициализация состояния итерации
|
||||
this.current = this.from;
|
||||
}
|
||||
|
||||
if (this.current <= this.to) {
|
||||
return {
|
||||
done: false,
|
||||
value: this.current++
|
||||
};
|
||||
} else {
|
||||
// очистка текущей итерации
|
||||
delete this.current;
|
||||
return {
|
||||
done: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
for (let num of range) {
|
||||
alert(num); // 1, затем 2, 3, 4, 5
|
||||
}
|
||||
|
||||
// Произойдёт вызов Math.max(1,2,3,4,5);
|
||||
alert( Math.max(...range) ); // 5 (*)
|
||||
|
||||
```
|
||||
|
||||
При таком подходе сам объект и хранит состояние итерации (текущий перебираемый элемент).
|
||||
|
||||
В данном случае это работает, но для большей гибкости и понятности кода рекомендуется, всё же, выделять итератор в отдельный объект со своим состоянием и кодом.
|
||||
|
||||
В последней строке `(*)` можно видеть, что итерируемый объект передаётся через spread для `Math.max`. При этом spread превращает итератор в массив. То есть пройдёт цикл по итератору, с вызовами `next`, и его результаты будут использованы в качестве списка аргументов.
|
||||
|
||||
[smart header="Бесконечные итераторы"]
|
||||
Возможны и бесконечные итераторы. Например, пример выше при `range.to = Infinity` будет таковым. Или можно сделать итератор, генерирующий бесконечную последовательность псевдослучайных чисел.
|
||||
|
||||
Нет никаких ограничений на `next`, он может возвращать всё новые и новые значения.
|
||||
|
||||
Разумеется, цикл `for..of` по такому итератору сам по себе не завершится, нужно его прерывать, например, через `break`.
|
||||
[/smart]
|
||||
|
||||
## Встроенные итераторы
|
||||
|
||||
Итератор можно получить и без `for..of`, прямым вызовом `Symbol.iterator`.
|
||||
|
||||
Например, этот код получает итератор для строки и вызывает его полностью "вручную":
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let str = "Hello";
|
||||
|
||||
// Делает то же, что и
|
||||
// for(var letter of str) alert(letter);
|
||||
|
||||
let iterator = str[Symbol.iterator]();
|
||||
|
||||
while(true) {
|
||||
let result = iterator.next();
|
||||
if (result.done) break;
|
||||
alert(result.value); // Выведет все буквы по очереди
|
||||
}
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>*Итератор* -- объект, предназначенный для перебора другого объекта.</li>
|
||||
<li>У итератора должен быть метод `next()`, возвращающий `{done: Boolean, value: any}`, где `value` -- очередное значение, а `done: true` в конце.</li>
|
||||
<li>Метод `Symbol.iterator` предназначен для получения итератора из объекта. Цикл `for..of` делает это автоматически, но можно и вызвать его напрямую.</li>
|
||||
<li>В современном стандарте есть много мест, где вместо массива используются более абстрактные "итерируемые" (со свойством `Symbol.iterator`) объекты.</li>
|
||||
<li>Встроенные объекты, такие как массивы и строки, являются итерируемыми, в соответствии с описанным выше.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|