- Операторы
- Управляющие инструкции
- JS Объекты
- Array
- Boolean
- Date
- Error
- Function
- Global
- JSON
- Math
- Number
- Object
- RegExp
- String
- Symbol
- Итераторы и генераторы
- Map и WeakMap
- Set и WeakSet
- Локализация
- браузер BOM
- HTML DOM
- События
- HTML Объекты
- Промисы, async/await
- Сетевые запросы
- Бинарные данные и файлы
- Разное
Итераторы и генераторы
Обработка каждого элемента коллекции является весьма распространённой операцией. JavaScript предоставляет несколько способов перебора коллекции, от простого цикла for
до map()
и filter()
. Итераторы и генераторы внедряют концепцию перебора непосредственно в ядро языка и обеспечивают механизм настройки поведения цикла for...of
.
Итераторы
Объект является итератором (перебираемым) , если он умеет обращаться к элементам коллекции по одному за раз, при этом отслеживая своё текущее положение внутри этой последовательности. В JavaScript итератор - это объект, который предоставляет метод next(), возвращающий следующий элемент последовательности. Этот метод возвращает объект с двумя свойствами:done
и value
:
- done
- Принимает значение
true
если итератор достиг конца итерируемой последовательности. В этом случае свойствоvalue
может определять возвращаемое значение итератора. - Принимает значение
false
если итератор может генерировать следующее значение последовательности. Это эквивалентно не указанному done.
- Принимает значение
- value
- любое JavaScript значение, возвращаемое итератором. Может быть опущено, если done имеет значение true.
Перебираемые (или итерируемые) объекты – это концепция, которая позволяет использовать любой объект в цикле for..of
.
Конечно же, сами массивы (Array) являются перебираемыми объектами. Но есть и много других встроенных перебираемых объектов: String, Map, Set, arguments. ...
Если объект не является массивом, но представляет собой коллекцию каких-то элементов, то удобно использовать цикл for..of
для их перебора
Чтобы сделать объект
итерируемым (и позволить for..of
работать с ним), нужно добавить в объект метод с именем Symbol.iterator
(специальный встроенный Symbol
, созданный как раз для этого).
- Когда цикл
for..of
запускается, он вызывает этот метод один раз (или выдаёт ошибку, если метод не найден). Этот метод должен вернуть итератор – объект с методомnext
. - Дальше
for..of
работает только с этим возвращённым объектом. - Когда
for..of
хочет получить следующее значение, он вызывает методnext()
этого объекта. - Результат вызова
next()
должен иметь вид{done: Boolean, value: any}
, гдеdone=true
означает, что итерация закончена, в противном случаеvalue
содержит очередное значение.
Пример
let range = { from: 1, to: 5 }; // 1. вызов for..of сначала вызывает эту функцию range[Symbol.iterator] = function() { // ...она возвращает объект итератора: // 2. Далее, for..of работает только с этим итератором, запрашивая у него новые значения return { current: this.from, last: this.to, // 3. next() вызывается на каждой итерации цикла for..of next() { // 4. он должен вернуть значение в виде объекта {done:.., value :...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; }; let s=''; for (let num of range) { s += num+' '; } alert(s); // 1 затем 2 3 4 5
Итерируемые объекты и псевдомассивы
Есть два официальных термина, которые очень похожи, но в то же время сильно различаются. Поэтому убедитесь, что вы как следует поняли их, чтобы избежать путаницы.
- Итерируемые объекты – это объекты, которые реализуют метод
Symbol.iterator
, как было описано выше. - Псевдомассивы – это объекты, у которых есть индексы и свойство
length
, то есть, они выглядят как массивы.
При использовании JavaScript в браузере или других окружениях мы можем встретить объекты, которые являются итерируемыми или псевдомассивами, или и тем, и другим.
Например, строки итерируемы (для них работает for..of
) и являются псевдомассивами (они индексированы и есть length
).
Но итерируемый объект может не быть псевдомассивом. И наоборот: псевдомассив может не быть итерируемым.
Генераторы
Генераторы -- новый вид функций в современном JavaScript. Они отличаются от обычных тем, что могут приостанавливать своё выполнение, возвращать промежуточный результат и далее возобновлять его позже, в произвольный момент времени.
Генераторы - это специальный тип функции, который работает как фабрика итераторов. Функция становится генератором, если содержит один или более yield операторов и использует синтаксическую конструкцию: function* (функция со звёздочкой). Её называют "функция-генератор" (generator function).yield
Ключевое слово yield используется для остановки и возобновления функций-генераторов (function*)..Синтаксис
[rv] = yield [[выражение]];
- выражение
- Возвращаемое выражение. Если не указано, то возвращается значение
undefined
. - rv
- Возвращает необязательное значение, которое передаётся в next() генератора, чтобы возобновить его выполнение.
Описание
Ключевое слово yield вызывает остановку функции-генератора и возвращает текущее значение выражения, указанного после ключевого слова yield. Его можно рассматривать как аналог ключевого слова return в функции-генераторе.
На самом деле ключевое слово yield возвращает объект с двумя параметрами, value и done. При этом, value является результатом вычисления выражения после yield, а done указывает, была ли завершена функция-генератор.
Во время остановки на операторе yield, выполнение кода в функции-генераторе не возобновится, пока не будет вызван метод next() возвращаемого функцией объекта-генератора. Это предоставляет непосредственный контроль за выполнением генератора и возвратом его значений.
Примеры
Следующий фрагмент кода содержит определение функции-генератора и вспомогательной функции:
function* foo(){ var index = 0; while(index <= 2) // при достижении 2, done в yield станет true, а value undefined; yield index++; } // После того как тело функции-генератора определено, оно может использоваться для получения итератора: var iterator = foo(); let a = iterator.next(); let s = '\n' + a.value + ', '+a.done; // { value:0, done:false } a = iterator.next(); s += '\n' + a.value + ', '+a.done; // { value:1, done:false } a = iterator.next(); s += '\n' + a.value + ', '+a.done; // { value:2, done:false } a = iterator.next(); s += '\n' + a.value + ', '+a.done; // { value:undefined, done:true } alert(s);
function*
function* (ключевое слово function со звёздочкой) определяет функцию-генератор.
Синтаксис
function* name([param[, param[, ... param]]]) { statements }
- name
- Имя функции.
- param
- Именованные аргументы функции (параметры). Функция-генератор может иметь 255 аргументов..
- statements
- Инструкции составляющие тело функции.. .
Описание
Генераторы являются функциями с возможностью выхода и последующего входа. Их контекст исполнения (значения переменных) сохраняется при последующих входах.
Когда вызывается функция-генератор, её тело исполняется не сразу; вместо этого возвращается объект-итератор. При вызове метода next() итератора тело функции-генератора исполняется до первого встреченного оператора yield, который определяет возвращаемое значение или делегирует дальнейшее выполнение другому генератору при помощи yield* anotherGenerator()
. Метод next() возвращает объект со свойством value, содержащим отданное значение, и свойством done, которое указывает, что генератор уже отдал своё последнее значение. Вызов метода next() с аргументом прекращает выполнение функции-генератора, и заменяет инструкцию yield на которой было приостановлено выполнение на аргумент переданный в next().
Примеры
Простой пример
function* idMaker() { var index = 0; while (index < 3) yield index++; } var gen = idMaker(); alert( ` ${ gen.next().value } ${ gen.next().value } ${ gen.next().value } ${ gen.next().value } `);
Пример с yield*
function* anotherGenerator(i) { yield i + 1; yield i + 2; yield i + 3; } function* generator(i) { yield i; yield* anotherGenerator(i); yield i + 10; } var gen = generator(10); alert( ` ${ gen.next().value } ${ gen.next().value } ${ gen.next().value } ${ gen.next().value } ${ gen.next().value } `);
Передача аргументов в генератор
function* logGenerator() { console.log(yield); console.log(yield); console.log(yield); } var gen = logGenerator(); // первый вызов next выполняется от начала функции // и до первого оператора yield gen.next(); gen.next('pretzel'); // pretzel gen.next('california'); // california gen.next('mayonnaise'); // mayonnaise
Инструкция return в генераторе
function* yieldAndReturn() { yield "Y"; return "R"; yield "unreachable"; } var gen = yieldAndReturn(); let a = gen.next(); let s = '\n' + a.value + ', '+a.done; // { value: "Y", done: false } a = gen.next(); s += '\n' + a.value + ', '+a.done; // { value: "R", done: true } a = gen.next(); s += '\n' + a.value + ', '+a.done; // { value: undefined, done: true } alert(s);