diff --git a/1-js/10-es-modern/16-generator/article.md b/1-js/10-es-modern/16-generator/article.md new file mode 100644 index 00000000..465a6242 --- /dev/null +++ b/1-js/10-es-modern/16-generator/article.md @@ -0,0 +1,335 @@ + +# Генераторы + +Генераторы -- новый вид функций в современном JavaScript. Они отличаются от обычных тем, что могут приостанавливать своё выполнение, возвращать промежуточный результат и далее возобновлять его позже, в произвольный момент времени. + +## Создание генератора + +Для объявления генератора используется новая синтаксическая конструкция: `function*` (функция со звёздочкой). + +Её называют "функция-генератор" (generator function). + +Выглядит это так: + +```js +function* generateSequence() { + yield 1; + yield 2; + return 3; +} +``` + +При запуске `generateSequence()` код такой функции не выполняется! + +Вместо этого она возвращает специальный объект, который как раз и называют "генератором". + +```js +// generator function создаёт generator +let generator = generateSequence(); +``` + +Правильнее всего будет воспринимать генератор как "замороженный вызов функции": + + + +При создании генератора код находится в начале своего выполнения. + +Основным методом генератора является `next()`. При вызове он возобновляет выполнение кода до ближайшего ключевого слова `yield`. По достижении `yield` выполнение приостанавливается, а значение -- возвращается во внешний код: + +```js +//+ run +'use strict'; + +function* generateSequence() { + yield 1; + yield 2; + return 3; +} + +let generator = generateSequence(); + +let one = generator.next(); + +alert(JSON.stringify(one)); // {value: 1, done: false} +``` + + + +Повторный вызов `generator.next()` возобновит выполнение и вернёт результат следующего `yield`: + +```js +let two = generator.next(); + +alert(JSON.stringify(two)); // {value: 2, done: false} +``` + + + +И, наконец, последний вызов завершит выполнение функции и вернёт результат `return`: + +```js +let three = generator.next(); + +alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*} +``` + + + + +Функция завершена. Внешний код должен увидить это из свойства `done:true` и прекратить вызовы. Впрочем, если новые вызовы `generator.next()` и будут, то они не вызовут ошибки, но будут возвращать один и тот же объект: `{done: true}`. + +"Открутить назад" завершившийся генератор нельзя, но можно создать новый ещё одним вызовом `generateSequence()` и выполнить его. + +## Генератор -- итератор + +Как вы, наверно, уже догадались по наличию метода `next()`, генератор является итерируемым объектом. + +Его можно перебирать и через `for..of`: + +```js +//+ run +'use strict'; + +function* generateSequence() { + yield 1; + yield 2; + return 3; +} + +let generator = generateSequence(); + +for(let value of generator) { + alert(value); // 1, затем 2 +} +``` + +Заметим, однако, существенную особенность такого перебора! + +При запуске примера выше будет выведено значение `1`, затем `2`. Значение `3` выведено не будет. Это потому что стандартные перебор итератора игнорирует `value` на последнем значении, при `done: true`. Так что результат `return` в цикле `for..of` не выводится. + +Соответственно, если мы хотим, чтобы все значения возвращались при переборе через `for..of`, то надо возвращать их через `yield`: + + +```js +//+ run +'use strict'; + +function* generateSequence() { + yield 1; + yield 2; +*!* + yield 3; +*/!* +} + +let generator = generateSequence(); + +for(let value of generator) { + alert(value); // 1, затем 2, затем 3 +} +``` + +...А зачем вообще `return` при таком раскладе, если его результат игнорируется? Он тоже нужен, но в других ситуациях. Перебор через `for..of` -- в некотором смысле "исключение". Как мы увидим дальше, в других контекстах `return` очень даже востребован. + +## Композиция генераторов + +Один генератор может включать в себя другие. Это называется композицией. + +Разберём композицию на примере. + +Пусть у нас есть функция `generateSequence`, которая генерирует последовательность чисел: + +```js +//+ run +'use strict'; + +function* generateSequence(start, end) { + + for (let i = start; i <= end; i++) { + yield i; + } + +} + +// Используем оператор … для преобразования итерируемого объекта в массив +let sequence = [...generateSequence(2,5)]; + +alert(sequence); // 2, 3, 4, 5 +``` + +Мы хотим на её основе сделать другую функцию `generateAlphaNumCodes()`, которая будет генерировать коды для буквенно-цифровых символов латинского алфавита: + + + +Далее этот набор кодов можно превратить в строку и использовать, к примеру, для выбора из него случайного пароля. Только символы пунктуации ещё хорошо бы добавить для надёжности, но в этом примере мы будем без них. + +Естественно, раз в нашем распоряжении есть готовый генератор `generateSequence`, то хорошо бы его использовать. + +Конечно, можно внутри `generateAlphaNum` запустить несколько `generateSequence`, объединить результаты и вернуть, но композиция -- это кое-что получше. + +Она выглядит так: + +```js +//+ run +'use strict'; + +function* generateSequence(start, end) { + for (let i = start; i <= end; i++) yield i; +} + +function* generateAlphaNum() { + +*!* + // 0..9 + yield* generateSequence(48, 57); + + // A..Z + yield* generateSequence(65, 90); + + // a..z + yield* generateSequence(97, 122); +*/!* + +} + +let str = ''; + +for(let code of generateAlphaNum()) { + str += String.fromCharCode(code); +} + +alert(str); // 0..9A..Za..z +``` + +Здесь использована специальная форма `yield*`. Она применима только к другому генератору и *делегирует* ему выполнение. + +То есть, при `yield*` интерпретатор переходит внутрь генератора-аргумента, к примеру, `generateSequence(48, 57)`, выполняет его, и все `yield`, которые он делает, выходят из внешнего генератора. + +Получается -- как будто мы вставили код внутреннего генератора во внешний напрямую, вот так: + +```js +//+ run +'use strict'; + +function* generateSequence(start, end) { + for (let i = start; i <= end; i++) yield i; +} + +function* generateAlphaNum() { + +*!* + // yield* generateSequence(48, 57); + for (let i = 48; i <= 57; i++) yield i; + + // yield* generateSequence(65, 90); + for (let i = 65; i <= 90; i++) yield i; + + // yield* generateSequence(97, 122); + for (let i = 97; i <= 122; i++) yield i; +*/!* + +} + +let str = ''; + +for(let code of generateAlphaNum()) { + str += String.fromCharCode(code); +} + +alert(str); // 0..9A..Za..z +``` + +## yield -- дорога в обе стороны + +До этого генераторы наиболее напоминали "итераторы на стероидах". Но, как мы сейчас увидим, это не так, есть фундаментальное различие, генераторы гораздо мощнее и гибче. + +Всё дело в том, что `yield` -- дорога в обе стороны: он не только возвращает результат наружу, но и может передавать значение извне в генератор. + +Вызов `let result = yield value` делает следующее: + + + +Продемонстрируем это на примере: + +```js +//+ run +'use strict'; + +function* gen() { +*!* + // Передать вопрос во внешний код и подождать ответа + let result = yield "Сколько будет 2 + 2?"; +*/!* + + alert(result); +} + +let generator = gen(); + +let question = generator.next().value; +// { value: "Сколько будет 2 + 2?", done: false } + +setTimeout(() => generator.next(4), 2000); +``` + + + +Выше проиллюстрировано то, что происходит в генераторе: + +
    +
  1. Первый `.next()` всегда без аргумента, он начинает выполнение и возвращает результат первого `yield`.
  2. +
  3. Результат `yield` переходит во внешний код (в `question`), он может выполнять любые асинхронные задачи.
  4. +
  5. Когда асинхронные задачи готовы, внешний код вызывает `.next(result)`, при этом выполнение продолжается, а `result` выходит из присваивания как результат `yield`.
  6. +
+ +Посмотрим вариант побольше: + +```js +//+ run +'use strict'; + +function* gen() { + let ask1 = yield "Сколько будет 2 + 2?"; + + alert(ask1); // 4 + + let ask2 = yield "А сколько будет 3 * 3?" + + alert(ask2); // 9 +} + +let generator = gen(); + +alert( generator.next().value ); // "...2+2?" + +alert( generator.next(4).value ); // "...3*3?" + +alert( generator.next(9).done ); // true +``` + + + +
    +
  1. Первый `.next()` начинает выполнение... Оно доходит до первого `yield`.
  2. +
  3. Результат возвращается во внешний код.
  4. +
  5. Второй `.next(4)` передаёт `4` обратно в генератор как результат первого `yield` и возобновляет выполнение.
  6. +
  7. ...Оно доходит до второго `yield`, который станет результатом `.next(4)`.
  8. +
  9. Третий `next(9)` передаёт `9` в генератор как результат второго `yield` и возобновляет выполнение, которое завершается окончанием функции, так что `done: true`.
  10. +
+ +Получается "пинг-понг": каждый `next(value)` передаёт в генератор значение, которое становится результатом текущего `yield`, возобновляет выполнение и получает выражение из следующего `yield`. + + + +Исключением является первый вызов `next`, который не может передать значение в генератор, т.к. ещё не было ни одного `yield`. + + + diff --git a/1-js/10-es-modern/16-generator/genYield2-2.png b/1-js/10-es-modern/16-generator/genYield2-2.png new file mode 100644 index 00000000..ea646d05 Binary files /dev/null and b/1-js/10-es-modern/16-generator/genYield2-2.png differ diff --git a/1-js/10-es-modern/16-generator/genYield2-2@2x.png b/1-js/10-es-modern/16-generator/genYield2-2@2x.png new file mode 100644 index 00000000..9aa7a21d Binary files /dev/null and b/1-js/10-es-modern/16-generator/genYield2-2@2x.png differ diff --git a/1-js/10-es-modern/16-generator/genYield2-3.png b/1-js/10-es-modern/16-generator/genYield2-3.png new file mode 100644 index 00000000..c1d48604 Binary files /dev/null and b/1-js/10-es-modern/16-generator/genYield2-3.png differ diff --git a/1-js/10-es-modern/16-generator/genYield2-3@2x.png b/1-js/10-es-modern/16-generator/genYield2-3@2x.png new file mode 100644 index 00000000..844742cb Binary files /dev/null and b/1-js/10-es-modern/16-generator/genYield2-3@2x.png differ diff --git a/1-js/10-es-modern/16-generator/genYield2.png b/1-js/10-es-modern/16-generator/genYield2.png new file mode 100644 index 00000000..5efec0b9 Binary files /dev/null and b/1-js/10-es-modern/16-generator/genYield2.png differ diff --git a/1-js/10-es-modern/16-generator/genYield2@2x.png b/1-js/10-es-modern/16-generator/genYield2@2x.png new file mode 100644 index 00000000..440263b5 Binary files /dev/null and b/1-js/10-es-modern/16-generator/genYield2@2x.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-1.png b/1-js/10-es-modern/16-generator/generateSequence-1.png new file mode 100644 index 00000000..c5016287 Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-1.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-1@2x.png b/1-js/10-es-modern/16-generator/generateSequence-1@2x.png new file mode 100644 index 00000000..2a1e0f68 Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-1@2x.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-2.png b/1-js/10-es-modern/16-generator/generateSequence-2.png new file mode 100644 index 00000000..65e1e58e Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-2.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-2@2x.png b/1-js/10-es-modern/16-generator/generateSequence-2@2x.png new file mode 100644 index 00000000..473c2ee4 Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-2@2x.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-3.png b/1-js/10-es-modern/16-generator/generateSequence-3.png new file mode 100644 index 00000000..8bc4418f Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-3.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-3@2x.png b/1-js/10-es-modern/16-generator/generateSequence-3@2x.png new file mode 100644 index 00000000..d28d52bf Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-3@2x.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-4.png b/1-js/10-es-modern/16-generator/generateSequence-4.png new file mode 100644 index 00000000..53064927 Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-4.png differ diff --git a/1-js/10-es-modern/16-generator/generateSequence-4@2x.png b/1-js/10-es-modern/16-generator/generateSequence-4@2x.png new file mode 100644 index 00000000..d19b217c Binary files /dev/null and b/1-js/10-es-modern/16-generator/generateSequence-4@2x.png differ diff --git a/1-js/10-es-modern/6-es-objects/article.md b/1-js/10-es-modern/6-es-object/article.md similarity index 100% rename from 1-js/10-es-modern/6-es-objects/article.md rename to 1-js/10-es-modern/6-es-object/article.md diff --git a/1-js/10-es-modern/7-es-classes/article.md b/1-js/10-es-modern/7-es-class/article.md similarity index 100% rename from 1-js/10-es-modern/7-es-classes/article.md rename to 1-js/10-es-modern/7-es-class/article.md diff --git a/1-js/10-es-modern/9-iterators/article.md b/1-js/10-es-modern/9-iterator/article.md similarity index 100% rename from 1-js/10-es-modern/9-iterators/article.md rename to 1-js/10-es-modern/9-iterator/article.md diff --git a/2-ui/3-event-details/9-keyboard-events/article.md b/2-ui/3-event-details/9-keyboard-events/article.md index 6fc0f579..815716ff 100644 --- a/2-ui/3-event-details/9-keyboard-events/article.md +++ b/2-ui/3-event-details/9-keyboard-events/article.md @@ -215,7 +215,7 @@ document.getElementById('only-upper').onkeypress = function(e) { [/online] -## Невосместимости [#keyboard-events-order] +## Несовместимости [#keyboard-events-order] Некоторые несовместимости в порядке срабатывания клавиатурных событий (когда что) ещё существуют. diff --git a/figures.sketch b/figures.sketch index dea8516d..40ea4688 100644 Binary files a/figures.sketch and b/figures.sketch differ