Что такое Iterator и Symbol.iterator в JavaScript?
Iterator и Symbol.iterator в JavaScript
Протокол итератора
Итератор (Iterator) — это объект, реализующий протокол итератора: у него должен быть метод next(), который при каждом вызове возвращает объект вида:
{ value: any, done: boolean }
value— текущее значение.done: false— итерация продолжается;done: true— итерация завершена.
Протокол итерируемого объекта
Чтобы объект стал итерируемым (iterable), он должен содержать метод с ключом Symbol.iterator. Этот метод вызывается без аргументов и должен возвращать итератор.
Встроенные итерируемые типы: Array, String, Set, Map, NodeList, аргументы функции (arguments).
Symbol.iterator
Symbol.iterator — это глобальный символ (Symbol), зарезервированный спецификацией. Движок JS автоматически ищет его при использовании:
for...of- spread-оператора
[...iterable] - деструктуризации
const [a, b] = iterable Array.from(iterable)Promise.all(iterable)и других API
Создание собственного итерируемого объекта
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
console.log([...range]); // [1, 2, 3, 4, 5]
Генераторы как удобный способ создания итераторов
Функции-генераторы автоматически реализуют оба протокола:
function* range(from, to) {
for (let i = from; i <= to; i++) {
yield i;
}
}
// Генератор возвращает объект, у которого есть и next(), и Symbol.iterator
const gen = range(1, 3);
console.log(gen.next()); // { value: 1, done: false }
console.log([...range(1, 3)]); // [1, 2, 3]
Отличие итерируемого от итератора
| Итерируемый (Iterable) | Итератор (Iterator) | |
|---|---|---|
| Ключевой метод | [Symbol.iterator]() | next() |
| Что возвращает | Итератор | {value, done} |
| Примеры | Array, Set, Map | array[Symbol.iterator]() |
Массив — итерируемый, но не итератор. array[Symbol.iterator]() — итератор.
Бесконечные итераторы
function* infiniteCounter() {
let i = 0;
while (true) {
yield i++;
}
}
const counter = infiniteCounter();
console.log(counter.next().value); // 0
console.log(counter.next().value); // 1
// Никогда не завершится — нужно контролировать вручную
Что хочет услышать интервьюер
Чёткое разграничение понятий: итерируемый объект (iterable) и итератор (iterator) — это разные вещи
Знание, что Symbol.iterator должен возвращать объект с методом next(), а next() должен возвращать {value, done}
Понимание, какие встроенные типы итерируемы (Array, String, Map, Set) и как это использовать
Умение реализовать собственный итерируемый объект с нуля или через генератор
Понимание, где движок неявно вызывает Symbol.iterator: for...of, spread, деструктуризация, Array.from
Пример: Ручная реализация итерируемого объекта
// Итерируемый диапазон чисел
const range = {
from: 1,
to: 5,
[Symbol.iterator](): Iterator<number> {
let current = this.from;
const last = this.to;
return {
next(): IteratorResult<number> {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined as unknown as number, done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1 2 3 4 5
}
console.log([...range]); // [1, 2, 3, 4, 5]
Пример: Генератор как итератор
// Генераторная функция автоматически реализует оба протокола
function* fibonacci(): Generator<number> {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
// Итератор: вызов next() вручную
console.log(fib.next()); // { value: 0, done: false }
console.log(fib.next()); // { value: 1, done: false }
console.log(fib.next()); // { value: 1, done: false }
// Берём первые 7 чисел Фибоначчи через spread с ограничением
function take<T>(iterable: Iterable<T>, n: number): T[] {
const result: T[] = [];
for (const item of iterable) {
result.push(item);
if (result.length >= n) break;
}
return result;
}
console.log(take(fibonacci(), 7)); // [0, 1, 1, 2, 3, 5, 8]
Пример: Проверка итерируемости и ручной вызов итератора
// Проверка, является ли объект итерируемым
function isIterable(obj: unknown): obj is Iterable<unknown> {
return obj != null && typeof (obj as Record<symbol, unknown>)[Symbol.iterator] === 'function';
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable('hello')); // true
console.log(isIterable(new Set())); // true
console.log(isIterable({ a: 1 })); // false
console.log(isIterable(42)); // false
// Ручной вызов итератора (так делает for...of «под капотом»)
const arr = ['a', 'b', 'c'];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Типичные ошибки
Путают итерируемый объект (имеет Symbol.iterator) и итератор (имеет next()) — это разные протоколы
Забывают, что Symbol.iterator — это символ, не строка; попытка сделать obj['Symbol.iterator'] не работает
Не возвращают правильную форму из next(): пропускают поле done или value, что ломает for...of
Думают, что обычный объект {} итерируемый — это не так, в отличие от массивов и строк
Смешивают итераторы и генераторы: генератор — удобный способ создать итератор, но не синоним


