Symbol.iterator в JavaScript

Symbol.iterator - это специальный ключ, по которому JavaScript ищет метод для перебора объекта.

Если у значения есть метод Symbol.iterator, такое значение считается итерируемым. Его можно использовать в for…of, операторе расширения и некоторых других конструкциях.

let numbers = [10, 20, 30];

console.log(typeof numbers[Symbol.iterator]);

Результат:

function

У массива есть метод Symbol.iterator, поэтому массив можно перебрать через for…of.

Что такое Symbol.iterator

Symbol.iterator - это встроенный символ. Он используется не как обычное имя свойства, а как специальный ключ.

Поэтому к нему обращаются через квадратные скобки:

let letters = ['a', 'b', 'c'];

console.log(letters[Symbol.iterator]);

Если написать через точку, JavaScript будет искать обычное свойство Symbol, а не встроенный символ:

console.log(letters.Symbol);

Результат:

undefined

Для Symbol.iterator нужна запись с квадратными скобками.

Как проверить, что объект итерируемый

Самый простой способ - проверить, есть ли у значения функция Symbol.iterator.

let name = 'Анна';
let user = { name: 'Анна' };

console.log(typeof name[Symbol.iterator]);
console.log(typeof user[Symbol.iterator]);

Результат:

function
undefined

Строка итерируемая, а обычный объект - нет.

Из-за этого строку можно перебрать через for…of:

for (let letter of 'JS') {
  console.log(letter);
}

А обычный объект напрямую так перебрать нельзя:

let user = {
  name: 'Анна',
  age: 25
};

for (let value of user) {
  console.log(value);
}

Такой код приведет к ошибке, потому что у объекта user нет метода Symbol.iterator.

Где используется Symbol.iterator

Обычно мы не вызываем Symbol.iterator вручную. JavaScript сам обращается к нему, когда нужно получить значения по одному.

Например, в цикле for…of:

let numbers = [1, 2, 3];

for (let number of numbers) {
  console.log(number);
}

В операторе расширения:

let numbers = [1, 2, 3];
let copy = [...numbers];

console.log(copy);

И в Array.from():

let letters = Array.from('код');

console.log(letters);

Все эти примеры работают с итерируемыми значениями.

Как это работает внутри

Метод Symbol.iterator возвращает специальный объект - итератор.

let numbers = [10, 20, 30];
let iterator = numbers[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Результат:

{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }

Метод next() отдает следующее значение. Когда значения закончились, done становится true.

Подробно next() разберем отдельно. Сейчас важно понять связь: Symbol.iterator возвращает итератор, а итератор умеет отдавать значения по одному.

Свой Symbol.iterator

Иногда объекту можно самому добавить Symbol.iterator. Тогда он станет итерируемым.

let range = {
  from: 1,
  to: 3,

  [Symbol.iterator]() {
    let current = this.from;
    let last = this.to;

    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }

        return { done: true };
      }
    };
  }
};

for (let number of range) {
  console.log(number);
}

Результат:

1
2
3

Объект range сам по себе обычный объект. Но мы добавили ему метод Symbol.iterator, поэтому for…of смог пройти по значениям от 1 до 3.

На практике свои итераторы пишут не каждый день. Но понимание этой механики помогает лучше разобраться, почему одни значения работают с for…of, а другие нет.

Итого

1. Symbol.iterator - это специальный ключ, по которому JavaScript находит метод перебора.

2. Если у значения есть метод Symbol.iterator, оно считается итерируемым.

3. for…of, оператор и Array.from() используют этот механизм.

4. Обычные объекты не итерируемые по умолчанию.

5. Метод Symbol.iterator возвращает итератор, у которого есть метод next().