С 2015 года JavaScript значительно улучшился.

Пользоваться им сейчас намного приятнее, чем когда-либо.

В этой статье мы рассмотрим итерируемые объекты JavaScript.

Скорость итерационного протокола

При его создании учитывалась скорость итерационного протокола.

Управление памятью происходит быстро при управлении небольшими объектами.

Итерация оптимизации движка JavaScript, чтобы не выделялись промежуточные объекты.

Повторное использование одного и того же итерационного объекта

Мы можем использовать итерацию несколько раз.

Итак, мы можем написать:

const results = [];
const iterator = [1, 2, 3][Symbol.iterator]();
while (!(val = iterator.next()).done) {
  results.push(val);
}

Получаем итератор из массива.

А затем мы вызвали его с помощью нашего цикла while, чтобы получить результаты.

Итерация

Существуют правила, которые регулируют итерационный протокол JavaScript.

Для метода next существуют некоторые правила.

Пока итератор возвращает значение next, возвращает объект со свойством value.

И done будет false.

Итак, у нас есть:

{ value: x, done: false }

После итерации последнего значения next должен вернуть объект, свойство done которого равно true.

Итерации, возвращающие итераторы

Итерируемые объекты могут возвращать новые итераторы или возвращать тот же итератор.

Если они возвращают свежие итераторы, то каждый из них возвращает значения с самого начала.

Итак, если у нас есть что-то вроде и array, и мы используем:

function getIterator(iterable) {
  return iterable[Symbol.iterator]();
}

чтобы получить итератор, мы можем сравнить их, чтобы увидеть, возвращают ли они один и тот же итератор.

Например, если у нас есть:

const arr = ['a', 'b'];
console.log(getIterator(arr) === getIterator(arr));

Тогда выражение будет записывать false.

Это означает, что даже если массив один и тот же, они возвращают один и тот же итератор.

Другие итераторы, такие как генераторы, каждый раз возвращают один и тот же итератор.

Если у нас есть объекты-генераторы, мы возвращаем один и тот же генератор каждый раз, когда он вызывается:

function* genFn() {
  yield 'foo';
  yield 'bar';
}
const gen = genFn();
console.log(getIterator(gen) === getIterator(gen));

genFn - это функция генератора, которая возвращает генератор,

И когда мы получаем итератор от генератора, мы получаем итератор от него и сравниваем их с логами выражения true.

Итак, один и тот же генератор имеет один и тот же итератор.

Мы можем перебирать новый итератор несколько раз.

Например, мы можем написать:

const arr = ['foo', 'bar', 'baz'];
for (const a of arr) {
  console.log(a);
}
for (const a of arr) {
  console.log(a);
}

чтобы дважды перебрать один и тот же массив.

С другой стороны, если у нас есть генератор:

function* genFn() {
  yield 'foo';
  yield 'bar';
}
const gen = genFn();
for (const a of gen) {
  console.log(a);
}
for (const a of gen) {
  console.log(a);
}

тогда мы проходим его только один раз, даже если у нас есть 2 цикла.

Закрытие итераторов

Итератор можно закрыть двумя способами.

Итератор может быть закрыт исчерпанием или закрытием.

Исчерпание - это когда итератор вернул все повторяемые значения.

Закрытие осуществляется вызовом return в функции итератора.

Когда мы звоним return, next не звонят.

return - необязательный метод.

Он есть не у всех итераторов.

Итераторы, у которых есть вызов return, называются закрываемыми.

return следует вызывать только в том случае, если итератор еще не исчерпан.

Это может произойти, если мы пройдем цикл for-of с break, continue, return или throw.

return должен создать объект, возвращающий { done: true, value: x }.

Заключение

Итерируемые объекты могут иметь разные вариации.

Они могут возвращать один итератор или несколько экземпляров.

Они также могут быть закрывающимися.