Что такое итератор? Зачем он нужен? и где я могу встретить его в JS?
Наверняка Вы уже при работе с массивами и списками сталкивались с необходимостью прохождения по каждому элемента данных объектов. На самом деле итераторы - это отдельные объекты позволяющие пройтись по элементам массивов, списков, словарей и т.д. (если проще, то всех итерируемых/перебираемых объектов). Соответственно, Вы встречали итератор при использовании того же цикла for..of :
for (let char of "Строка для перебора") {
alert(char); // при отработке данного цикла мы увидим каждый символ строки отдельно, мы посимвольно переберём строку
}
Итераторы бывают встроенные и наши собственные. Под нашими собственными итераторами подразумевается объекты разрешающие перебрать элементы итерируемого объекта указанным нами алгоритмом, например возьмём пример отсюдова :
Допустим, у нас есть некий объект, который надо «умным способом» перебрать.
Например, 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 видит только значения.Такое отделение функционала перебора от самого объекта даёт дополнительную гибкость. Например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток. Однако, бывают ситуации когда оно не нужно.
Встроенные итераторы предоставляются мы можем получить явно вызвав Symbol.iterator и вот пример его использования напрямую:
'use strict';
let str = "Hello";
// Делает то же, что и
// for (var letter of str) alert(letter);let iterator = strSymbol.iterator;
while(true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // Выведет все буквы по очереди
}