es6
|
@ -1,7 +1,7 @@
|
|||
|
||||
# Set, Map, WeakSet и WeakMap
|
||||
|
||||
Новые типы коллекций в JavaScript: `Set`, `Map`, `WeakSet` и `WeakMap`.
|
||||
В ES-2015 появились новые типы коллекций в JavaScript: `Set`, `Map`, `WeakSet` и `WeakMap`.
|
||||
|
||||
|
||||
## Map
|
||||
|
@ -16,23 +16,32 @@
|
|||
|
||||
let map = new Map();
|
||||
|
||||
map.set('1', 'str1'); // строка
|
||||
map
|
||||
.set(1, 'num1') // число
|
||||
.set(true, 'bool1'); // булевое
|
||||
map.set('1', 'str1'); // ключ-строка
|
||||
map.set(1, 'num1'); // число
|
||||
map.set(true, 'bool1'); // булевое значение
|
||||
|
||||
// в обычном объекте это было бы одно и то же
|
||||
// в обычном объекте это было бы одно и то же,
|
||||
// map сохраняет тип ключа
|
||||
alert( map.get(1) ); // 'num1'
|
||||
alert( map.get('1') ); // 'str1'
|
||||
|
||||
alert( map.size ); // 3
|
||||
```
|
||||
|
||||
Как видно из примера выше, для сохранения и чтения значений используются методы `get` и `set`, причём `set` можно чейнить. И ключи и значения сохраняются "как есть", без преобразований типов.
|
||||
Как видно из примера выше, для сохранения и чтения значений используются методы `get` и `set`. И ключи и значения сохраняются "как есть", без преобразований типов.
|
||||
|
||||
Свойство `map.size` хранит общее количество записей в `map`.
|
||||
|
||||
**При создании `Map` можно сразу инициализовать списком значений.**
|
||||
Метод `set` можно чейнить:
|
||||
|
||||
```js
|
||||
map
|
||||
.set('1', 'str1')
|
||||
.set(1, 'num1')
|
||||
.set(true, 'bool1');
|
||||
```
|
||||
|
||||
При создании `Map` можно сразу инициализовать списком значений.
|
||||
|
||||
Объект `map` с тремя ключами, как и в примере выше:
|
||||
|
||||
|
@ -44,9 +53,9 @@ let map = new Map([
|
|||
]);
|
||||
```
|
||||
|
||||
Аргументом `new Map` должен быть итерируемый объект (не обязательно именно массив), которые должен возвратить объект с ключами `0`,`1` -- также не обязательно массив. Везде утиная типизация, максимальная гибкость.
|
||||
Аргументом `new Map` должен быть итерируемый объект (не обязательно именно массив). Везде утиная типизация, максимальная гибкость.
|
||||
|
||||
**В качестве ключей можно использовать и объекты:**
|
||||
**В качестве ключей `map` можно использовать и объекты:**
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -54,6 +63,7 @@ let map = new Map([
|
|||
|
||||
let user = { name: "Вася" };
|
||||
|
||||
// для каждого пользователя будем хранить количество посещений
|
||||
let visitsCountMap = new Map();
|
||||
|
||||
*!*
|
||||
|
@ -65,12 +75,12 @@ alert( visitsCountMap.get(user) ); // 123
|
|||
```
|
||||
|
||||
[smart header="Как map сравнивает ключи"]
|
||||
Для проверки значений на эквивалентность используется алгоритм [SameValueZero](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevaluezero). Он аналогичен строгому равенству `===`, отличие -- в том, что `NaN` считается равным `NaN`.
|
||||
Для проверки значений на эквивалентность используется алгоритм [SameValueZero](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevaluezero). Он аналогичен строгому равенству `===`, отличие -- в том, что `NaN` считается равным `NaN`. Поэтому значение `NaN` также может быть использовано в качестве ключа.
|
||||
|
||||
Этот алгоритм жёстко фиксирован в стандарте, его нельзя изменять или задавать свою функцию для него.
|
||||
Этот алгоритм жёстко фиксирован в стандарте, его нельзя изменять или задавать свою функцию сравнения.
|
||||
[/smart]
|
||||
|
||||
Для удаления записей:
|
||||
Методы для удаления записей:
|
||||
<ul>
|
||||
<li>`map.delete(key)` удаляет запись с ключом `key`, возвращает `true`, если такая запись была, иначе `false`.</li>
|
||||
<li>`map.clear()` -- удаляет все записи, очищает `map`.</li>
|
||||
|
@ -82,22 +92,9 @@ alert( visitsCountMap.get(user) ); // 123
|
|||
<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
|
||||
```
|
||||
|
||||
### Итерация
|
||||
|
||||
Для итерации используется один из трёх методов:
|
||||
Для итерации по `map` используется один из трёх методов:
|
||||
<ul>
|
||||
<li>`map.keys()` -- возвращает итерируемый объект для ключей,</li>
|
||||
<li>`map.values()` -- возвращает итерируемый объект для значений,</li>
|
||||
|
@ -116,14 +113,17 @@ let recipeMap = new Map([
|
|||
['сметаны', '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 значения
|
||||
}
|
||||
|
@ -150,19 +150,11 @@ recipeMap.forEach( (value, key, map) => {
|
|||
});
|
||||
```
|
||||
|
||||
|
||||
У `Map` есть и другие свой методы:
|
||||
|
||||
<ul>
|
||||
<li>`map.size()` -- возвращает количество записей,</li>
|
||||
<li>`map.clear()` -- удаляет все записи.</li>
|
||||
</ul>
|
||||
|
||||
## Set
|
||||
|
||||
`Set` -- коллекция для хранения множества значений, причём каждое значение может встречаться лишь один раз.
|
||||
|
||||
Например, к нам приходят посетители, и мы хотели бы сохранять всех, кто пришёл. Повторные визиты не должны приводить к дубликатам.
|
||||
Например, к нам приходят посетители, и мы хотели бы сохранять всех, кто пришёл. При этом повторные визиты не должны приводить к дубликатам, то есть каждого посетителя нужно "посчитать" ровно один раз.
|
||||
|
||||
`Set` для этого отлично подходит:
|
||||
|
||||
|
@ -176,13 +168,14 @@ let vasya = {name: "Вася"};
|
|||
let petya = {name: "Петя"};
|
||||
let dasha = {name: "Даша"};
|
||||
|
||||
// посещения
|
||||
// посещения, некоторые пользователи заходят много раз
|
||||
set.add(vasya);
|
||||
set.add(petya);
|
||||
set.add(dasha);
|
||||
set.add(vasya);
|
||||
set.add(petya);
|
||||
|
||||
// set сохраняет только уникальные значения
|
||||
alert( set.size ); // 3
|
||||
|
||||
set.forEach( user => alert(user.name ) ); // Вася, Петя, Даша
|
||||
|
@ -190,12 +183,12 @@ set.forEach( user => alert(user.name ) ); // Вася, Петя, Даша
|
|||
|
||||
В примере выше многократные добавления одного и того же объекта в `set` не создают лишних копий.
|
||||
|
||||
Альтернатива `Set` -- это массивы с поиском дубликата при каждом добавлении, но это гораздо хуже по производительности. Или же объекты, где в качестве ключа выступает какой-нибудь уникальный идентификатор посетителя. Но это менее удобно, чем простой и наглядный `Set`.
|
||||
Альтернатива `Set` -- это массивы с поиском дубликата при каждом добавлении, но они гораздо хуже по производительности. Или можно использовать обычные объекты, где в качестве ключа выступает какой-нибудь уникальный идентификатор посетителя. Но это менее удобно, чем простой и наглядный `Set`.
|
||||
|
||||
Основные методы:
|
||||
<ul>
|
||||
<li>`set.add(item)` -- добавляет в коллекцию `item`, возвращает `set` (чейнится).</li>
|
||||
<li>`set.delete(item) -- удаляет `item` из коллекции, возвращает `true`, если он там был, иначе `false`.</li>
|
||||
<li>`set.delete(item)` -- удаляет `item` из коллекции, возвращает `true`, если он там был, иначе `false`.</li>
|
||||
<li>`set.has(item)` -- возвращает `true`, если `item` есть в коллекции, иначе `false`.</li>
|
||||
<li>`set.clear()` -- очищает `set`.</li>
|
||||
</ul>
|
||||
|
@ -214,17 +207,17 @@ set.forEach((value, valueAgain, set) => {
|
|||
});
|
||||
```
|
||||
|
||||
Заметим, что в `Map` у функции в `.forEach` три аргумента: ключ, значение, объект `map`.
|
||||
Заметим, что в `Set` у функции в `.forEach` три аргумента: значение, ещё раз значение, и затем сам перебираемый объект `set`. При этом значение повторяется в аргументах два раза.
|
||||
|
||||
В `Set` для совместимости с `Map` сделано похожим образом -- у `.forEach`-функции также три аргумента. Но первые два всегда совпадают и содержат очередное значение множества.
|
||||
Так сделано для совместимости с `Map`, где у `.forEach`-функции также три аргумента. Но в `Set` первые два всегда совпадают и содержат очередное значение множества.
|
||||
|
||||
## WeakMap и WeakSet
|
||||
|
||||
`WeakSet` -- особый вид `Set` не препятствующий сборщику мусора. То же самое -- `WeakMap` для `Map`.
|
||||
`WeakSet` -- особый вид `Set` не препятствующий сборщику мусора удалять свои элементы. То же самое -- `WeakMap` для `Map`.
|
||||
|
||||
То есть, если некий объект присутствует только в `WeakSet/WeakMap` -- он удаляется из памяти.
|
||||
|
||||
Это нужно для тех ситуаций, когда сами объекты используются где-то в другом месте кода, а здесь мы хотим хранить для них "вспомогательные" данные, существующие лишь пока жив объект.
|
||||
Это нужно для тех ситуаций, когда основное место для хранения и использования объектов находится где-то в другом месте кода, а здесь мы хотим хранить для них "вспомогательные" данные, существующие лишь пока жив объект.
|
||||
|
||||
Например, у нас есть элементы на странице или, к примеру, пользователи, и мы хотим хранить для них вспомогательную инфомацию, например обработчики событий или просто данные, но действительные лишь пока объект, к которому они относятся, существует.
|
||||
|
||||
|
@ -260,6 +253,8 @@ activeUsers.splice(0, 1); // Петя более не активный поль
|
|||
// weakMap теперь содержит только 1 элемент
|
||||
```
|
||||
|
||||
Таким образом, `WeakMap` избавляет нас от необходимости вручную удалять вспомогательные данные, когда удалён основной объект.
|
||||
|
||||
У WeakMap есть ряд ограничений:
|
||||
<ul>
|
||||
<li>Нет свойства `size`.</li>
|
||||
|
@ -269,7 +264,7 @@ activeUsers.splice(0, 1); // Петя более не активный поль
|
|||
|
||||
Иными словами, `WeakMap` работает только на запись (`set`, `delete`) и чтение (`get`, `has`) элементов по конкретному ключу, а не как полноценная коллекция. Нельзя вывести всё содержимое `WeakMap`, нет соответствующих методов.
|
||||
|
||||
Это связано с тем, что содержимое `WeakMap` может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. Нет какого-то конкретного момента, когда такая очистка точно произойдёт -- это определяется внутренними алгоритмами сборщика и его сведениями о системе.
|
||||
Это связано с тем, что содержимое `WeakMap` может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. В равной степени он не гарантирует и обратное. Нет какого-то конкретного момента, когда такая очистка точно произойдёт -- это определяется внутренними алгоритмами сборщика и его сведениями о системе.
|
||||
|
||||
Поэтому содержимое `WeakMap` в произвольный момент, строго говоря, не определено. Может быть, сборщик мусора уже удалил какие-то записи, а может и нет. С этим, а также с требованиями к эффективной реализации `WeakMap`, и связано отсутствие методов, осуществляющих доступ ко всем записям.
|
||||
|
||||
|
@ -286,22 +281,4 @@ activeUsers.splice(0, 1); // Петя более не активный поль
|
|||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
27
1-js/10-es-modern/11-promise/2-promise-sequence/solution.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
Для последовательной загрузки нужно организовать промисы в цепочку, чтобы они выполнялись строго -- один после другого.
|
||||
|
||||
Вот код, который это делает:
|
||||
|
||||
```js
|
||||
// начало цепочки
|
||||
let chain = Promise.resolve();
|
||||
|
||||
let results = [];
|
||||
|
||||
// в цикле добавляем задачи в цепочку
|
||||
urls.forEach(function(url) {
|
||||
chain = chain
|
||||
.then(() => httpGet(url))
|
||||
.then((result) => {
|
||||
results.push(result);
|
||||
});
|
||||
});
|
||||
|
||||
// в конце — выводим результаты
|
||||
chain.then(() => {
|
||||
alert(results);
|
||||
});
|
||||
```
|
||||
|
||||
Использование `Promise.resolve()` как начала асинхронной цепочки -- очень распространённый приём.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
function httpGet(url) {
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
|
||||
xhr.onload = function() {
|
||||
if (this.status == 200) {
|
||||
resolve(this.response);
|
||||
} else {
|
||||
var error = new Error(this.statusText);
|
||||
error.code = this.status;
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
reject(new Error("Network Error"));
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script src="httpGet.js"></script>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
let urls = ['guest.json', 'user.json'];
|
||||
|
||||
let chain = Promise.resolve();
|
||||
|
||||
let results = [];
|
||||
|
||||
urls.forEach(function(url) {
|
||||
chain = chain
|
||||
.then(() => httpGet(url))
|
||||
.then((result) => {
|
||||
results.push(result);
|
||||
});
|
||||
});
|
||||
|
||||
chain.then(() => {
|
||||
alert(results);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "guest",
|
||||
"isAdmin": false
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
function httpGet(url) {
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
|
||||
xhr.onload = function() {
|
||||
if (this.status == 200) {
|
||||
resolve(this.response);
|
||||
} else {
|
||||
var error = new Error(this.statusText);
|
||||
error.code = this.status;
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
reject(new Error("Network Error"));
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script src="httpGet.js"></script>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
let urls = ['guest.json', 'user.json'];
|
||||
|
||||
// ... ваш код
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
26
1-js/10-es-modern/11-promise/2-promise-sequence/task.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
# Загрузить массив последовательно
|
||||
|
||||
Есть массив URL:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let urls = [
|
||||
'user.json',
|
||||
'guest.json'
|
||||
];
|
||||
```
|
||||
|
||||
Напишите код, который все URL из этого массива загружает -- один за другим (последовательно), и сохраняет в результаты в массиве `results`, а потом выводит.
|
||||
|
||||
Вариант с параллельной загрузкой выглядел бы так:
|
||||
|
||||
```js
|
||||
Promise.all( urls.map(httpGet) )
|
||||
.then(alert);
|
||||
```
|
||||
|
||||
В этой задаче загрузку нужно реализовать последовательно.
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
@ -39,7 +39,7 @@ var promise = new Promise(function(resolve, reject) {
|
|||
|
||||
Универсальный метод для навешивания обработчиков:
|
||||
|
||||
```
|
||||
```js
|
||||
promise.then(onFulfilled, onRejected)
|
||||
```
|
||||
|
||||
|
@ -51,9 +51,9 @@ promise.then(onFulfilled, onRejected)
|
|||
С его помощью можно назначить как оба обработчика сразу, так и только один:
|
||||
|
||||
```js
|
||||
// только на успешное выполнение
|
||||
// onFulfilled сработает при успешном выполнении
|
||||
promise.then(onFulfilled)
|
||||
// только на ошибку
|
||||
// onRejected сработает при ошибке
|
||||
promise.then(null, onRejected)
|
||||
```
|
||||
|
||||
|
@ -65,7 +65,9 @@ promise.then(null, onRejected)
|
|||
Если в функции промиса происходит синхронный `throw` (или иная ошибка), то вызывается `reject`:
|
||||
```js
|
||||
//+ run
|
||||
var p = new Promise((resolve, reject) => {
|
||||
'use strict';
|
||||
|
||||
let p = new Promise((resolve, reject) => {
|
||||
// то же что reject(new Error("o_O"))
|
||||
throw new Error("o_O");
|
||||
})
|
||||
|
@ -83,8 +85,10 @@ p.catch(alert); // Error: o_O
|
|||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
// Создаётся объект promise
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
setTimeout(() => {
|
||||
// переведёт промис в состояние fulfilled с результатом "result"
|
||||
|
@ -135,7 +139,7 @@ promise
|
|||
|
||||
```
|
||||
|
||||
Конечно, вместо `setTimeout` мог бы быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс.
|
||||
Конечно, вместо `setTimeout` внутри функции промиса может быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс. Главное, чтобы по своему завершению он вызвал `resolve` или `reject`, которые передадут результат обработчикам.
|
||||
|
||||
[smart header="Только один аргумент"]
|
||||
Функции `resolve/reject` принимают ровно один аргумент -- результат/ошибку.
|
||||
|
@ -153,7 +157,9 @@ promise
|
|||
|
||||
```js
|
||||
//+ run
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
'use strict';
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
*!*
|
||||
// через 1 секунду готов результат: result
|
||||
|
@ -184,7 +190,9 @@ promise
|
|||
|
||||
```js
|
||||
//+ run
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
'use strict';
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
// reject вызван раньше, resolve будет проигнорирован
|
||||
setTimeout(() => reject(new Error("error")), 1000);
|
||||
|
@ -262,7 +270,7 @@ httpGet("/article/promise/user.json")
|
|||
|
||||
|
||||
[smart header="Метод `fetch`"]
|
||||
Заметим, что ряд современных браузеров уже поддерживает [fetch](/fetch) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он, конечно, гораздо мощнее, чем `httpGet`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
|
||||
Заметим, что ряд современных браузеров уже поддерживает [fetch](/fetch) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он гораздо мощнее, чем `httpGet`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
|
||||
[/smart]
|
||||
|
||||
|
||||
|
@ -332,15 +340,15 @@ httpGet(...)
|
|||
.then(...)
|
||||
```
|
||||
|
||||
При чейнинге, то есть последовательных вызовах `.then...then..then`, в каждый следующий `then` переходит результат от предыдущего.
|
||||
При чейнинге, то есть последовательных вызовах `.then…then…then`, в каждый следующий `then` переходит результат от предыдущего. Вызовы `console.log` оставлены, чтобы при запуске можно было посмотреть конкретные значения, хотя они здесь и не очень важны.
|
||||
|
||||
**Причём, если очередной `then` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.**
|
||||
**Если очередной `then` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.**
|
||||
|
||||
В коде выше:
|
||||
|
||||
<ol>
|
||||
<li>В первом `then` возвращается объект `user`, он переходит в следующий `then`.</li>
|
||||
<li>Во втором `then` возвращается промис (результат `loadUser`). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий `then`.</li>
|
||||
<li>Во втором `then` возвращается промис (результат нового вызова `httpGet`). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий `then`.</li>
|
||||
<li>Третий `then` ничего не возвращает.</li>
|
||||
</ol>
|
||||
|
||||
|
@ -348,13 +356,19 @@ httpGet(...)
|
|||
|
||||
<img src="promiseUserFlow.png">
|
||||
|
||||
Значком "песочные часы" помечены периоды ожидания. Если `then` возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки ждёт.
|
||||
Значком "песочные часы" помечены периоды ожидания, которых всего два: в исходном `httpGet` и в подвызове далее по цепочке.
|
||||
|
||||
Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис.
|
||||
Если `then` возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки будет ждать.
|
||||
|
||||
Это стандартный приём. Если внутри `then` стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис, который перейдёт в состояние "выполнен" после `setTimeout`.
|
||||
То есть, логика довольно проста:
|
||||
<ul>
|
||||
<li>В каждом `then` мы получаем текущий результат работы.</li>
|
||||
<li>Можно его обработать синхронно и вернуть результат (например, применить `JSON.parse`). Или же, если нужна асинхронная обработка -- инициировать её и вернуть промис.</li>
|
||||
</ul>
|
||||
|
||||
Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис. Это общее правило: если внутри `then` стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.
|
||||
|
||||
В данном случае промис должен перейти в состояние "выполнен" после срабатывания `setTimeout`.
|
||||
|
||||
Строку `(*)` для этого нужно переписать так:
|
||||
```js
|
||||
|
@ -369,7 +383,9 @@ httpGet(...)
|
|||
// после таймаута — вызов resolve,
|
||||
// можно без результата, чтобы управление перешло в следующий then
|
||||
// (или можно передать данные пользователя дальше по цепочке)
|
||||
*!*
|
||||
resolve();
|
||||
*/!*
|
||||
}, 3000);
|
||||
});
|
||||
})
|
||||
|
@ -433,10 +449,10 @@ httpGet('/page-not-exists')
|
|||
[smart header="А что после `catch`?"]
|
||||
Обработчик `.catch(onRejected)` получает ошибку и должен обработать её.
|
||||
|
||||
Здесь два варианта развития событий:
|
||||
Есть два варианта развития событий:
|
||||
<ol>
|
||||
<li>Если ошибка не критичная, то обработчик возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.</li>
|
||||
<li>Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в ближайший `.catch(onRejected)`.
|
||||
<li>Если ошибка не критичная, то `onRejected` возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.</li>
|
||||
<li>Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в следующий ближайший `.catch(onRejected)`.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
@ -462,11 +478,11 @@ httpGet('/page-not-exists')
|
|||
|
||||
<img src="promiseEcma.png">
|
||||
|
||||
Когда функция-executor вызывает reject или resolve, то `PromiseState` становится `"resolved"` или `"rejected"`, а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь `"PromiseJobs"`.
|
||||
Когда функция-executor вызывает `reject` или `resolve`, то `PromiseState` становится `"resolved"` или `"rejected"`, а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь `"PromiseJobs"`.
|
||||
|
||||
Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`.
|
||||
Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции-обработчики выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`.
|
||||
|
||||
Исключение из этого правила -- если `resolve` возвращает другой Promise. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
|
||||
Исключение из этого правила -- если `resolve` возвращает другой `Promise`. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
|
||||
|
||||
Добавляет обработчики в списки один метод: `.then(onResolved, onRejected)`. Метод `.catch(onRejected)` -- всего лишь сокращённая запись `.then(null, onRejected)`.
|
||||
|
||||
|
@ -519,7 +535,9 @@ promise.then( function f2(result) {
|
|||
|
||||
<img src="promiseTwo.png">
|
||||
|
||||
На этой иллюстрации можно увидеть, что `.then` если один из обработчиков не указан, добавляет его "от себя", следующим образом:
|
||||
На этой иллюстрации можно увидеть добавленные нами обработчики `f1`, `f2`, а также -- автоматические добавленные обработчики ошибок `"Thrower"`.
|
||||
|
||||
Дело в том, что `.then`, если один из обработчиков не указан, добавляет его "от себя", следующим образом:
|
||||
<ul>
|
||||
<li>Для успешного выполнения -- функция `Identity`, которая выглядит как `arg => return arg`, то есть возвращает аргумент без изменений.</li>
|
||||
<li>Для ошибки -- функция `Thrower`, которая выглядит как `arg => throw arg`, то есть генерирует ошибку.</li>
|
||||
|
@ -529,15 +547,19 @@ promise.then( function f2(result) {
|
|||
|
||||
Обратим внимание, в этом примере намеренно *не используется чейнинг*. То есть, обработчики добавляются именно на один и тот же промис.
|
||||
|
||||
Поэтому оба `alert` выдадут одно значение `1`. Алгоритм такой -- все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками одного промиса нет, а сам результат промиса (`PromiseResult`) после установки не меняется.
|
||||
Поэтому оба `alert` выдадут одно значение `1`.
|
||||
|
||||
Все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками в рамках одного промиса нет, а сам результат промиса (`PromiseResult`) после установки не меняется.
|
||||
|
||||
Поэтому, чтобы продолжить работу с результатом, используется чейнинг.
|
||||
|
||||
**Для того, чтобы результат обработчика передать следующей функции, `.then` создаёт новый промис и возвращает его.**
|
||||
|
||||
В примере выше создаётся два таких промиса, каждый из которых даёт свою ветку выполнения:
|
||||
В примере выше создаётся два таких промиса (т.к. два вызова `.then`), каждый из которых даёт свою ветку выполнения:
|
||||
|
||||
<img src="promiseTwoThen.png">
|
||||
|
||||
Изначально эти новые промисы -- пустые. Когда в будущем выполнятся обработчики `f1, f2`, то их результат будет передан в новые промисы по стандартному принципу:
|
||||
Изначально эти новые промисы -- "пустые", они ждут. Когда в будущем выполнятся обработчики `f1, f2`, то их результат будет передан в новые промисы по стандартному принципу:
|
||||
|
||||
<ul>
|
||||
<li>Если вернётся обычное значение (не промис), новый промис перейдёт в `"resolved"` с ним.</li>
|
||||
|
@ -547,6 +569,8 @@ promise.then( function f2(result) {
|
|||
|
||||
<img src="promiseHandlerVariants.png">
|
||||
|
||||
Дальше выполнятся уже обработчики на новом промисе, и так далее.
|
||||
|
||||
Чтобы лучше понять происходящее, посмотрим на цепочку, которая получается в процессе написания кода для показа github-аватара.
|
||||
|
||||
Первый промис и обработка его результата:
|
||||
|
@ -561,20 +585,16 @@ httpGet('/article/promise/user.json')
|
|||
|
||||
Если промис завершился через `resolve`, то результат -- в `JSON.parse`, если `reject` -- то в Thrower.
|
||||
|
||||
Как было сказано выше, `Thrower` -- это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан. Можно сказать, что второй обработчик выглядит так:
|
||||
Как было сказано выше, `Thrower` -- это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан.
|
||||
|
||||
Можно считать, что второй обработчик выглядит так:
|
||||
|
||||
```js
|
||||
*!*
|
||||
function thrower(err) {
|
||||
throw err;
|
||||
}
|
||||
*/!*
|
||||
|
||||
httpGet('/article/promise/user.json')
|
||||
.then(JSON.parse, thrower)
|
||||
.then(JSON.parse, *!*err => throw err*/!*)
|
||||
```
|
||||
|
||||
Заметим, что когда обработчик в промисах делает `throw`, то ошибка не "валит" скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик `onRejected`.
|
||||
Заметим, что когда обработчик в промисах делает `throw` -- в данном случае, при ошибке запроса, то такая ошибка не "валит" скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик `onRejected`.
|
||||
|
||||
Добавим в код ещё строку:
|
||||
|
||||
|
@ -663,8 +683,12 @@ httpGet('/article/promise/userNoGithub.json')
|
|||
|
||||
В консоли тоже ничего не будет, так как ошибка остаётся "внутри" промиса, ожидая добавления следующего обработчика `onRejected`, которому будет передана.
|
||||
|
||||
Итак, мы рассмотрели основные приёмы использования промисов. Далее -- посмотрим некоторые полезные вспомогательные методы.
|
||||
|
||||
## Параллельное выполнение
|
||||
|
||||
Что, если мы хотим осуществить несколько асинхронных процессов одновременно и обработать их результат?
|
||||
|
||||
## Вспомогательные методы
|
||||
|
||||
В классе `Promise` есть следующие статические методы.
|
||||
|
||||
|
@ -681,8 +705,41 @@ Promise.all([
|
|||
httpGet('/article/promise/user.json'),
|
||||
httpGet('/article/promise/guest.json')
|
||||
]).then(results => {
|
||||
results = results.map(JSON.parse);
|
||||
alert( results[0].name + ', ' + results[1].name ); // iliakan, guest
|
||||
alert(results);
|
||||
});
|
||||
```
|
||||
|
||||
Допустим, у нас есть массив с URL.
|
||||
|
||||
```js
|
||||
let urls = [
|
||||
'/article/promise/user.json',
|
||||
'/article/promise/guest.json'
|
||||
];
|
||||
```
|
||||
|
||||
Чтобы загрузить их параллельно, нужно:
|
||||
<ol>
|
||||
<li>Создать для каждого URL соответствующий промис.</li>
|
||||
<li>Обернуть массив таких промисов в `Promise.all`.</li>
|
||||
</ol>
|
||||
|
||||
Получится так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let urls = [
|
||||
'/article/promise/user.json',
|
||||
'/article/promise/guest.json'
|
||||
];
|
||||
|
||||
*!*
|
||||
Promise.all( urls.map(httpGet) )
|
||||
*/!*
|
||||
.then(results => {
|
||||
alert(results);
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -706,7 +763,7 @@ Promise.all([
|
|||
|
||||
### Promise.race(iterable)
|
||||
|
||||
Как и `Promise.all` получает итерируемый объект с промисами и возвращает новый промис.
|
||||
Вызов `Promise.race`, как и `Promise.all`, получает итерируемый объект с промисами, которые нужно выполнить, и возвращает новый промис.
|
||||
|
||||
Но, в отличие от `Promise.all`, результатом будет только первый успешно выполнившийся промис из списка. Остальные игнорируются.
|
||||
|
||||
|
@ -724,7 +781,7 @@ Promise.race([
|
|||
});
|
||||
```
|
||||
|
||||
### Promise.resolve(value)
|
||||
## Promise.resolve(value)
|
||||
|
||||
Вызов `Promise.resolve(value)` создаёт успешно выполнившийся промис с результатом `value`.
|
||||
|
||||
|
@ -746,7 +803,7 @@ Promise.resolve(window.location) // начать с этого значения
|
|||
.then(alert) // и вывести результат
|
||||
```
|
||||
|
||||
### Promise.reject(error)
|
||||
## Promise.reject(error)
|
||||
|
||||
Аналогично `Promise.resolve(value)` создаёт уже выполнившийся промис, но не с успешным результатом, а с ошибкой `error`.
|
||||
|
||||
|
@ -760,7 +817,7 @@ Promise.reject(new Error("..."))
|
|||
|
||||
Метод `Promise.reject` используется очень редко, гораздо реже чем `resolve`, потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения.
|
||||
|
||||
### Итого
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Промис -- это специальный объект, который хранит своё состояние, текущий результат (если есть) и коллбэки.</li>
|
||||
|
@ -770,12 +827,9 @@ Promise.reject(new Error("..."))
|
|||
<li>Для передачи результата от одного обработчика к другому используется чейнинг.</li>
|
||||
</ul>
|
||||
|
||||
В современной JavaScript-разработки промисы в явном виде используются, как ни странно, довольно редко.
|
||||
|
||||
Тем не менее, понимать промисы нужно обязательно, так как бывают ситуации, когда без них сложно.
|
||||
|
||||
Промисы служат основой для более продвинутых способов написания асинхронного кода, использующих генераторы. Мы рассмотрим их далее в этом разделе.
|
||||
У промисов есть некоторые ограничения. В частности, стандарт не предусматривает какой-то метод для "отмены" промиса, хотя в ряде ситуаций (http-запросы) это было бы довольно удобно. Возможно, он появится в следующей версии стандарта JavaScript.
|
||||
|
||||
В современной JavaScript-разработке сложные цепочки с промисами используются редко, так как они куда проще описываются при помощи генераторов с библиотекой `co`, которые рассмотрены в [соответствующей главе](/generator). Можно сказать, что промисы лежат в основе более продвинутых способов асинхронной разработки.
|
||||
|
||||
[head]
|
||||
<style>
|
4
1-js/10-es-modern/11-promise/guest.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "guest",
|
||||
"isAdmin": false
|
||||
}
|
BIN
1-js/10-es-modern/11-promise/promiseEcma.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
1-js/10-es-modern/11-promise/promiseEcma@2x.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
1-js/10-es-modern/11-promise/promiseHandlerVariants.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
1-js/10-es-modern/11-promise/promiseHandlerVariants@2x.png
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
1-js/10-es-modern/11-promise/promiseInit.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
1-js/10-es-modern/11-promise/promiseInit@2x.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-1.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-1@2x.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-2.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-2@2x.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-3.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-3@2x.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-4.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
1-js/10-es-modern/11-promise/promiseLoadAvatarChain-4@2x.png
Normal file
After Width: | Height: | Size: 116 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 |
BIN
1-js/10-es-modern/11-promise/promiseTwo.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
1-js/10-es-modern/11-promise/promiseTwo@2x.png
Normal file
After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
BIN
1-js/10-es-modern/11-promise/promiseUserFlow.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
1-js/10-es-modern/11-promise/promiseUserFlow@2x.png
Normal file
After Width: | Height: | Size: 71 KiB |
4
1-js/10-es-modern/11-promise/user.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "iliakan",
|
||||
"isAdmin": true
|
||||
}
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
BIN
1-js/10-es-modern/12-generator/genYield2-2.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
1-js/10-es-modern/12-generator/genYield2-2@2x.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
1-js/10-es-modern/12-generator/genYield2-3.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
1-js/10-es-modern/12-generator/genYield2-3@2x.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
1-js/10-es-modern/12-generator/genYield2.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
1-js/10-es-modern/12-generator/genYield2@2x.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-1.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-1@2x.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-2.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-2@2x.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-3.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-3@2x.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-4.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
1-js/10-es-modern/12-generator/generateSequence-4@2x.png
Normal file
After Width: | Height: | Size: 31 KiB |
4
1-js/10-es-modern/12-generator/user.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "iliakan",
|
||||
"isAdmin": true
|
||||
}
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 76 KiB |
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name": "an-unknown-person-32662",
|
||||
"isAdmin": false
|
||||
}
|
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 9 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 29 KiB |
|
@ -3,46 +3,54 @@
|
|||
|
||||
Новый примитивный тип данных Symbol служит для создания уникальных идентификаторов.
|
||||
|
||||
Мы вначале рассмотрим объявление и особенности символов, а затем -- их использование.
|
||||
|
||||
## Объявление
|
||||
|
||||
Синтаксис:
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let name = Symbol();
|
||||
alert( typeof name ); // symbol
|
||||
let sym = Symbol();
|
||||
```
|
||||
|
||||
Обратим внимание, не `new Symbol`, а просто `Symbol`, так как это -- примитив.
|
||||
|
||||
Каждый символ -- уникален.
|
||||
|
||||
У функции `Symbol` есть необязательный аргумент "имя символа". Можно его использовать для описания символа, в целях отладки:
|
||||
У символов есть и соответствующий `typeof`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let name = Symbol("name");
|
||||
alert( name.toString() ); // Symbol(name)
|
||||
let sym = Symbol();
|
||||
alert( typeof sym ); // symbol
|
||||
```
|
||||
|
||||
При этом если у двух символов одинаковое имя, то они *не равны*:
|
||||
|
||||
Каждый символ -- уникален. У функции `Symbol` есть необязательный аргумент "имя символа". Можно его использовать для описания символа, в целях отладки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let sym = Symbol("name");
|
||||
alert( sym.toString() ); // Symbol(name)
|
||||
```
|
||||
|
||||
...Но при этом если у двух символов одинаковое имя, то это не значит, что они равны:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( Symbol("name") == Symbol("name") ); // false
|
||||
```
|
||||
|
||||
То есть, ещё раз заметим, что каждый символ -- уникален.
|
||||
Если хочется из разных частей программы использовать именно одинаковый символ, то можно передавать между ними объект символа или же -- использовать "глобальные символы" и "реестр глобальных символов", которые мы рассмотрим далее.
|
||||
|
||||
## Глобальные символы
|
||||
|
||||
Существует "глобальный реестр" символов, который позволяет, при необходимости, разделять символы между частями программы.
|
||||
|
||||
Для чтения (или создания, если нет) "глобального" символа служит вызов `Symbol.for(имя)`:
|
||||
Для чтения (или создания, при отсутствии) "глобального" символа служит вызов `Symbol.for(имя)`.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -55,7 +63,7 @@ let name = Symbol.for("name");
|
|||
alert( Symbol.for("name") == name ); // true
|
||||
```
|
||||
|
||||
Вызов `Symbol.keyFor(sym)` позволяет получить по глобальному символу его имя:
|
||||
Вызов `Symbol.for` возвращает символ по имени. Обратным для него является вызов `Symbol.keyFor(sym)` позволяет получить по глобальному символу его имя:
|
||||
|
||||
|
||||
```js
|
||||
|
@ -78,13 +86,13 @@ alert( Symbol.keyFor(name) ); // name
|
|||
alert( Symbol.keyFor(Symbol.for("name")) ); // name, глобальный
|
||||
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный символ
|
||||
```
|
||||
|
||||
Таким образом, имя символа, если этот символ не глобальный, не имеет особого применения, оно полезно лишь в целях вывода и отладки.
|
||||
[/warn]
|
||||
|
||||
## Использование символов
|
||||
|
||||
Символы используются в качестве имён для методов и свойств объекта, которые являются системными, или которые необходимо скрыть.
|
||||
|
||||
Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации:
|
||||
Символы можно использовать в качестве имён для свойств объекта, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -94,17 +102,38 @@ let isAdmin = Symbol("isAdmin");
|
|||
|
||||
let user = {
|
||||
name: "Вася",
|
||||
age: 30,
|
||||
[isAdmin]: true
|
||||
};
|
||||
|
||||
alert( Object.keys(user) ); // name, age
|
||||
for(let key in user) alert(key); // name, age
|
||||
alert(user[isAdmin]); // true
|
||||
```
|
||||
|
||||
В примере выше выведутся все свойства, кроме символьного.
|
||||
Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации:
|
||||
|
||||
В современно JavaScript есть много системных символов. Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1). В спецификации принято символы для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`.
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let user = {
|
||||
name: "Вася",
|
||||
age: 30,
|
||||
[Symbol.for("isAdmin")]: true
|
||||
};
|
||||
|
||||
// в цикле for..in также не будет символа
|
||||
alert( Object.keys(user) ); // name, age
|
||||
|
||||
// доступ к свойству через глобальный символ — работает
|
||||
alert( user[Symbol.for("isAdmin")] );
|
||||
```
|
||||
|
||||
Кроме того, свойство-символ недоступно, если обратиться к его названию: `user.isAdmin` не существует.
|
||||
|
||||
Зачем всё это, почему не просто использовать строки?
|
||||
|
||||
Резонный вопрос. На ум могут прийти соображения производительности, так как символы -- это по сути специальные идентификаторы, они компактнее, чем строка. Но при современных оптимизациях объектов это редко имеет значение.
|
||||
|
||||
Самое широкое применение символов предусмотрено внутри самого стандарта JavaScript. В современном стандарте есть много системных символов. Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1). В спецификации принято символы для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`.
|
||||
|
||||
Например:
|
||||
<ul>
|
||||
|
@ -113,35 +142,41 @@ for(let key in user) alert(key); // name, age
|
|||
<li>...и т.п.</li>
|
||||
</ul>
|
||||
|
||||
Мы легко поймём смысл введения нового типа "символ", если поставим себя на место создателей языка JavaScript.
|
||||
**Мы легко поймём смысл введения нового типа "символ", если поставим себя на место создателей языка JavaScript.**
|
||||
|
||||
Допустим, надо добавить к объекту "особый" функционал, например, функцию, которая задаёт преобразование объекта к примитиву.
|
||||
Допустим, в новом стандарте нам надо добавить к объекту "особый" функционал, например, функцию, которая задаёт преобразование объекта к примитиву. Как `obj.toString`, но для преобразования в примитивы.
|
||||
|
||||
Можно, конечно, сказать, что "свойство obj.toPrimitive теперь системное, оно делает то-то и то-то". Но это опасно. Мало ли, вполне возможно, что свойство с таким именем уже используется в существующем коде. И если сделать его системным, то он сломается.
|
||||
Мы ведь не можем просто сказать, что "свойство obj.toPrimitive теперь будет задавать преобразование к примитиву и автоматически вызываться в таких-то ситуациях". Это опасно. Мы не можем так просто взять и придать особый смысл свойству. Мало ли, вполне возможно, что свойство с таким именем уже используется в существующем коде, и если сделать его особым, то он сломается.
|
||||
|
||||
Нельзя просто взять и зарезервировать какие-то свойства существующих объектов для нового функционала.
|
||||
|
||||
Поэтому ввели целый тип "символы". Их можно использовать для задания свойств, которые уникальны, не участвуют в итерации и заведомо не конфликтуют со старым кодом.
|
||||
Поэтому ввели целый тип "символы". Их можно использовать для задания таких свойств, так как они:
|
||||
<ul>
|
||||
<li>а) уникальны,</li>
|
||||
<li>б) не участвуют в циклах,</li>
|
||||
<li>в) заведомо не сломают старый код, который о них слыхом не слыхивал.</li>
|
||||
</ul>
|
||||
|
||||
Продемонстрируем отсутствие конфликта для нового системного свойства `Symbol.iterator`:
|
||||
|
||||
Например:
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
|
||||
let obj = {
|
||||
iterator: 1,
|
||||
[Symbol.iterator]: function() {}
|
||||
[Symbol.iterator]() {}
|
||||
}
|
||||
|
||||
alert(obj.iterator); // 1
|
||||
alert(obj[Symbol.iterator]) // function, символ не конфликтует
|
||||
```
|
||||
|
||||
Выше мы использовали системный символ `Symbol.iterator`, поскольку он один из самых широко поддерживаемых. Мы подробно разберём его смысл в следующих главах, пока же -- это просто пример символа.
|
||||
Выше мы использовали системный символ `Symbol.iterator`, поскольку он один из самых широко поддерживаемых. Мы подробно разберём его смысл в главе про [итераторы](/iterator), пока же -- это просто пример символа.
|
||||
|
||||
Чтобы получить все символы объекта, есть особый вызов [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols).
|
||||
|
||||
Эта функция возвращает все символы в объекте. Заметим, что `getOwnPropertyNames` символы не возвращает.
|
||||
Эта функция возвращает все символы в объекте (и только их). Заметим, что старая функция `getOwnPropertyNames` символы не возвращает, что опять же гарантирует отсутствие конфликтов со старым кодом.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -167,7 +202,9 @@ alert( Object.getOwnPropertyNames(obj) ); // iterator
|
|||
<li>Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`.</li>
|
||||
</ul>
|
||||
|
||||
Основная область использования символов -- это системные свойства объектов. Поддержка у них пока небольшая, но она растёт. Системные символы позволяют добавлять в стандарт новые "особые" свойства объектов, при этом не резервируя соответствующие названия.
|
||||
Основная область использования символов -- это системные свойства объектов, которые задают разные аспекты их поведения. Поддержка у них пока небольшая, но она растёт. Системные символы позволяют разработчикам стандарта добавлять новые "особые" свойства объектов, при этом не резервируя соответствующие строковые значения.
|
||||
|
||||
Но, конечно, мы можем создавать и свои локальные и глобальные символы, использовать их в своих объектах.
|
||||
Системные символы доступны как свойства функции `Symbol`, например `Symbol.iterator`.
|
||||
|
||||
Мы можем создавать и свои символы, использовать их в объектах. Записывать их как свойства `Symbol`, разумеется, нельзя, если нужен глобально доступный символ, то используется `Symbol.for(имя)`.
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
Итерируемые или, иными словами, "перебираемые" объекты -- это те, содержимое которых можно перебрать в цикле.
|
||||
|
||||
Например, массив, но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов), а итераторы дают возможность сделать "перебираемыми" любые объекты.
|
||||
Например, перебираемым объектом является массив. Но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов).
|
||||
|
||||
Для перебора таких объектов добавлен новый синтаксис цикла: `for..of`.
|
||||
|
||||
|
@ -38,22 +38,29 @@ for(let char of "Привет") {
|
|||
|
||||
Практически везде, где нужен перебор, он осуществляется через итераторы. Это включает в себя не только строки, массивы, но и вызов функции с оператором spread `f(...args)`, и многое другое.
|
||||
|
||||
В отличие от массивов, "перебираемые" объекты могут не иметь "длины" `length`. Как мы увидим далее, итераторы дают возможность сделать "перебираемыми" любые объекты.
|
||||
|
||||
## Свой итератор
|
||||
|
||||
Допустим, у нас есть некий объект, который надо "умным способом" перебрать.
|
||||
|
||||
Например, `range` -- диапазон чисел от `range.from` до `range.to`:
|
||||
Например, `range` -- диапазон чисел от `from` до `to`, и мы хотим, чтобы `for(let num of range)` "перебирал", этот объект. При этом под перебором мы подразумеваем перечисление чисел от `from` до `to`.
|
||||
|
||||
Объект `range` без итератора:
|
||||
|
||||
```js
|
||||
let range = {
|
||||
from: 1,
|
||||
to: 5
|
||||
};
|
||||
|
||||
// хотим сделать перебор
|
||||
// for(let num of range) ...
|
||||
```
|
||||
|
||||
Для возможности использовать объект в `for..of` ему ставится свойство с названием `Symbol.iterator`. `Symbol.iterator` -- системный символ, который вызывает `for..of` в начале выполнения.
|
||||
Для возможности использовать объект в `for..of` нужно создать в нём свойство с названием `Symbol.iterator` (системный символ).
|
||||
|
||||
При вызове метода `Symbol.iterator` перебираемый (итерируемый) объект должен возвращать другой объект ("итератор"), который умеет осуществлять перебор.
|
||||
При вызове метода `Symbol.iterator` перебираемый объект должен возвращать другой объект ("итератор"), который умеет осуществлять перебор.
|
||||
|
||||
По стандарту у такого объекта должен быть метод `next()`, который при каждом вызове возвращает очередное значение и окончен ли перебор.
|
||||
|
||||
|
@ -97,11 +104,11 @@ for (let num of range) {
|
|||
}
|
||||
```
|
||||
|
||||
...То есть, здесь имеет место разделение сущностей:
|
||||
Как видно из кода выше, здесь имеет место разделение сущностей:
|
||||
|
||||
<ul>
|
||||
<li>Перебираемый объект сам не реализует методы для своего перебора. Для этого существует другой объект, который хранит текущее состояние перебора и возвращает значение.</li>
|
||||
<li>Этот объект называется итератором и создаётся при вызове метода `Symbol.iterator`.</li>
|
||||
<li>Перебираемый объект `range` сам не реализует методы для своего перебора.</li>
|
||||
<li>Для этого создаётся другой объект, который хранит текущее состояние перебора и возвращает значение. Этот объект называется итератором и возвращается при вызове метода `range[Symbol.iterator]`.</li>
|
||||
<li>У итератора должен быть метод `next()`, который при каждом вызове возвращает объект со свойствами:
|
||||
<ul>
|
||||
<li>`value` -- очередное значение,
|
||||
|
@ -110,11 +117,11 @@ for (let num of range) {
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
Конструкция `for..of` получает итератор и вызывает метод `next()` прозрачно и автоматически до получения `done: true`.
|
||||
Конструкция `for..of` в начале своего выполнения автоматически вызывает `Symbol.iterator()`, получает итератор и далее вызывает метод `next()` до получения `done: true`. Такова внутренняя механика. Внешний код при переборе через `for..of` видит только значения.
|
||||
|
||||
Впрочем, хоть такое отделение функционала перебора от самого объекта даёт дополнительную гибкость, например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток, зачастую оно не нужно.
|
||||
Такое отделение функционала перебора от самого объекта даёт дополнительную гибкость, например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток. Однако, бывают ситуации, когда оно не нужно.
|
||||
|
||||
Чтобы функционал по перебору заключить в самом объекте, можно вернуть `this` в качестве итератора:
|
||||
Если функционал по перебору (метод `next`) предоставляется самим объектом, то можно вернуть `this` в качестве итератора:
|
||||
|
||||
|
||||
```js
|
||||
|
@ -166,19 +173,23 @@ alert( Math.max(...range) ); // 5 (*)
|
|||
|
||||
В данном случае это работает, но для большей гибкости и понятности кода рекомендуется, всё же, выделять итератор в отдельный объект со своим состоянием и кодом.
|
||||
|
||||
В последней строке `(*)` можно видеть, что итерируемый объект передаётся через spread для `Math.max`. При этом spread превращает итератор в массив. То есть пройдёт цикл по итератору, с вызовами `next`, и его результаты будут использованы в качестве списка аргументов.
|
||||
[smart header="Оператор spread `...` и итераторы"]
|
||||
В последней строке `(*)` примера выше можно видеть, что итерируемый объект передаётся через spread для `Math.max`.
|
||||
|
||||
При этом `...range` автоматически превращает итерируемый объект в массив. То есть произойдёт цикл `for..of` по `range`, и его результаты будут использованы в качестве списка аргументов.
|
||||
[/smart]
|
||||
|
||||
[smart header="Бесконечные итераторы"]
|
||||
Возможны и бесконечные итераторы. Например, пример выше при `range.to = Infinity` будет таковым. Или можно сделать итератор, генерирующий бесконечную последовательность псевдослучайных чисел.
|
||||
Возможны и бесконечные итераторы. Например, пример выше при `range.to = Infinity` будет таковым. Или можно сделать итератор, генерирующий бесконечную последовательность псевдослучайных чисел. Тоже полезно.
|
||||
|
||||
Нет никаких ограничений на `next`, он может возвращать всё новые и новые значения.
|
||||
Нет никаких ограничений на `next`, он может возвращать всё новые и новые значения, и это нормально.
|
||||
|
||||
Разумеется, цикл `for..of` по такому итератору сам по себе не завершится, нужно его прерывать, например, через `break`.
|
||||
Разумеется, цикл `for..of` по такому итератору тоже будет бесконечным, нужно его прерывать, например, через `break`.
|
||||
[/smart]
|
||||
|
||||
## Встроенные итераторы
|
||||
|
||||
Итератор можно получить и без `for..of`, прямым вызовом `Symbol.iterator`.
|
||||
Встроенные в JavaScript итераторы можно получить и явным образом, без `for..of`, прямым вызовом `Symbol.iterator`.
|
||||
|
||||
Например, этот код получает итератор для строки и вызывает его полностью "вручную":
|
||||
|
||||
|
@ -200,13 +211,15 @@ while(true) {
|
|||
}
|
||||
```
|
||||
|
||||
То же самое будет работать и для массивов.
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>*Итератор* -- объект, предназначенный для перебора другого объекта.</li>
|
||||
<li>У итератора должен быть метод `next()`, возвращающий `{done: Boolean, value: any}`, где `value` -- очередное значение, а `done: true` в конце.</li>
|
||||
<li>Метод `Symbol.iterator` предназначен для получения итератора из объекта. Цикл `for..of` делает это автоматически, но можно и вызвать его напрямую.</li>
|
||||
<li>В современном стандарте есть много мест, где вместо массива используются более абстрактные "итерируемые" (со свойством `Symbol.iterator`) объекты.</li>
|
||||
<li>В современном стандарте есть много мест, где вместо массива используются более абстрактные "итерируемые" (со свойством `Symbol.iterator`) объекты, например оператор spread `...`.</li>
|
||||
<li>Встроенные объекты, такие как массивы и строки, являются итерируемыми, в соответствии с описанным выше.</li>
|
||||
</ul>
|
||||
|
||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |