Мы хотим сделать этот проект с открытым исходным кодом доступным для людей во всем мире. Пожалуйста, помогите нам перевести это руководство на другие языки.
КупитьEPUB/PDF
Поделиться
18 февраля 2020 г.

Итераторы

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/iterable.

В современный JavaScript добавлена новая концепция «итерируемых» (iterable) объектов.

Итерируемые или, иными словами, «перебираемые» объекты – это те, содержимое которых можно перебрать в цикле.

Например, перебираемым объектом является массив. Но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов).

Для перебора таких объектов добавлен новый синтаксис цикла: for..of.

Например:

'use strict';
let arr = [1, 2, 3]; // массив — пример итерируемого объекта
for (let value of arr) {
 alert(value); // 1, затем 2, затем 3
}

Также итерируемой является строка:

'use strict';
for (let char of "Привет") {
 alert(char); // Выведет по одной букве: П, р, и, в, е, т
}

Итераторы – расширяющая понятие «массив» концепция, которая пронизывает современный стандарт JavaScript сверху донизу.

Практически везде, где нужен перебор, он осуществляется через итераторы. Это включает в себя не только строки, массивы, но и вызов функции с оператором spread f(...args), и многое другое.

В отличие от массивов, «перебираемые» объекты могут не иметь «длины» length. Как мы увидим далее, итераторы дают возможность сделать «перебираемыми» любые объекты.

Свой итератор

Допустим, у нас есть некий объект, который надо «умным способом» перебрать.

Например, range – диапазон чисел от from до to, и мы хотим, чтобы for (let num of range) «перебирал» этот объект. При этом под перебором мы подразумеваем перечисление чисел от from до to.

Объект range без итератора:

let range = {
 from: 1,
 to: 5
};
// хотим сделать перебор
// for (let num of range) ...

Для возможности использовать объект в for..of нужно создать в нём свойство с названием Symbol.iterator (системный символ).

При вызове метода Symbol.iterator перебираемый объект должен возвращать другой объект («итератор»), который умеет осуществлять перебор.

По стандарту у такого объекта должен быть метод next(), который при каждом вызове возвращает очередное значение и проверяет, окончен ли перебор.

В коде это выглядит следующим образом:

'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
}

Как видно из кода выше, здесь имеет место разделение сущностей:

  • Перебираемый объект range сам не реализует методы для своего перебора.
  • Для этого создаётся другой объект, который хранит текущее состояние перебора и возвращает значение. Этот объект называется итератором и возвращается при вызове метода range[Symbol.iterator].
  • У итератора должен быть метод next(), который при каждом вызове возвращает объект со свойствами:
    • value – очередное значение,
    • done – равно false если есть ещё значения, и true – в конце.

Конструкция for..of в начале своего выполнения автоматически вызывает Symbol.iterator(), получает итератор и далее вызывает метод next() до получения done: true. Такова внутренняя механика. Внешний код при переборе через for..of видит только значения.

Такое отделение функциональности перебора от самого объекта даёт дополнительную гибкость. Например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток. Однако, бывают ситуации когда оно не нужно.

Если функциональность по перебору (метод next) предоставляется самим объектом, то можно вернуть this в качестве итератора:

'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 ... и итераторы

В последней строке (*) примера выше можно видеть, что итерируемый объект передаётся через spread для Math.max.

При этом ...range интерпретируется как последовательность чисел. То есть произойдёт цикл for..of по range, и его результаты будут использованы в качестве списка аргументов.

Бесконечные итераторы

Возможны и бесконечные итераторы. Например, пример выше при range.to = Infinity будет таковым. Или можно сделать итератор, генерирующий бесконечную последовательность псевдослучайных чисел. Тоже полезно.

Нет никаких ограничений на next, он может возвращать всё новые и новые значения, и это нормально.

Разумеется, цикл for..of по такому итератору тоже будет бесконечным, нужно его прерывать, например, через break.

Встроенные итераторы

Встроенные в JavaScript итераторы можно получить и явным образом, без for..of, прямым вызовом Symbol.iterator.

Например, этот код получает итератор для строки и вызывает его полностью «вручную»:

'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); // Выведет все буквы по очереди
}

То же самое будет работать и для массивов.

Итого

  • Итератор – объект, предназначенный для перебора другого объекта.
  • У итератора должен быть метод next(), возвращающий объект {done: Boolean, value: any}, где value – очередное значение, а done: true в конце.
  • Метод Symbol.iterator предназначен для получения итератора из объекта. Цикл for..of делает это автоматически, но можно и вызвать его напрямую.
  • В современном стандарте есть много мест, где вместо массива используются более абстрактные «итерируемые» (со свойством Symbol.iterator) объекты, например оператор spread ....
  • Встроенные объекты, такие как массивы и строки, являются итерируемыми, в соответствии с описанным выше.
Поделиться

Комментарии

перед тем как писать...
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen...)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.

AltStyle によって変換されたページ (->オリジナル) /