This commit is contained in:
Ilya Kantor 2015-07-07 15:06:37 +03:00
parent d300d60699
commit c978fe73c1
34 changed files with 379 additions and 25 deletions

View 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

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before After
Before After

View file

@ -289,37 +289,18 @@ new Rabbit();
```
## Итого
<ul>
<li>Классы можно объявлять как в основном потоке кода, так и "инлайн", по аналогии с Function Declaration и Expression.</li>
<li>В объявлении классов можно использовать методы, геттеры/сеттеры и вычислимые названия методов.</li>
<li>При наследовании вызов конструктора родителя осуществлятся через `super(...args)`, вызов родительских методов -- через `super.method(...args)`.</li>
</ul>
Концепция классов, которая после долгих обсуждений получилась в стандарте EcmaScript, носит название "максимально минимальной". То есть, в неё вошли только те возможности, которые уж точно необходимы.
В частности, не вошли "приватные" и "защищённые" свойства. То есть, все свойства и методы класса технически доступны снаружи. Возможно, они появятся в будущих редакциях стандарта.
В классах нет возможности ограничивать

View 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>