Итератор - что это?


#1

Что такое итератор? Зачем он нужен? и где я могу встретить его в JS?


#2

Наверняка Вы уже при работе с массивами и списками сталкивались с необходимостью прохождения по каждому элемента данных объектов. На самом деле итераторы - это отдельные объекты позволяющие пройтись по элементам массивов, списков, словарей и т.д. (если проще, то всех итерируемых/перебираемых объектов). Соответственно, Вы встречали итератор при использовании того же цикла 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); // Выведет все буквы по очереди
}