Итераторы и протокол итерации
Протокол итерации — соглашение JavaScript, по которому объект считается итерируемым (iterable), если у него есть метод
Symbol.iterator, возвращающий итератор с методомnext, который возвращает{ value, done }.
Зачем нужно
Протокол итерации лежит в основе for...of, spread-оператора, деструктуризации, Array.from, Promise.all и других конструкций ES6+. Понимание протокола позволяет создавать собственные итерируемые структуры данных и работать с генераторами.
Где используется
for...ofработает с любым итерируемым объектом- Spread
[...iterable]и деструктуризацияconst [a, b] = iterable - Кастомные коллекции: диапазоны чисел, деревья, графы
- Генераторы реализуют оба протокола автоматически
Протоколы
Iterable protocol — объект с методом [Symbol.iterator], возвращающим итератор.
Iterator protocol — объект с методом next, возвращающим { value: any, done: boolean }.
// Встроенные итерируемые: Array, String, Map, Set, NodeList
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator];
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Создание кастомного итератора
Диапазон чисел (Range)
function range(start, end, step = 1) {
return {
[Symbol.iterator] {
let current = start;
return {
next {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
};
}
// Теперь range итерируемый:
for (const n of range(1, 5)) {
console.log(n); // 1, 2, 3, 4, 5
}
console.log([...range(0, 10, 2)]); // [0, 2, 4, 6, 8, 10]
const [first, second] = range(10, 20, 5); // 10, 15
Итерируемый класс
class LinkedList {
#head = null;
append(value) {
this.#head = { value, next: this.#head };
return this;
}
[Symbol.iterator] {
let current = this.#head;
return {
next {
if (current) {
const value = current.value;
current = current.next();
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const list = new LinkedList();
list.append(3).append(2).append(1);
for (const val of list) {
console.log(val); // 1, 2, 3
}
console.log([...list]); // [1, 2, 3]
Генератор как итератор
// Генераторы реализуют оба протокола автоматически
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
for (const n of range(1, 5)) {
console.log(n); // 1, 2, 3, 4, 5
}
Частые ошибки
- Итератор ≠ итерируемый — итератор имеет
next, итерируемый —Symbol.iterator; генераторы совмещают оба протокола, обычные итераторы — нет. - Однократное использование итератора — итератор отслеживает состояние; повторно перебрать его нельзя, нужно создать новый через
[Symbol.iterator]. - Бесконечный итератор без ограничения — итератор без
done: trueбесконечен;[...infiniteRange]вызовет зависание.
Связанные темы
- _MOC JavaScript
- _MOC Асинхронность
- Асинхронные генераторы
- Деструктуризация массивов
- Методы массивов -- map, filter, reduce
Ресурсы
🎓 Источник: Итерирование, циклы и итераторы в JavaScript
- 📅 2018-11-05 · YouTube
- Тезисы: iterator pattern из GoF реализован в JS как protocol.
for...of, spread, destructuring,Array.from— все они используютSymbol.iterator. Один объект может быть iterator И iterable (return this в Symbol.iterator)
🎓 Источник: Итераторы и асинхронные итераторы в JavaScript
- 📅 2019-03-05 · YouTube
- Тезисы:
Symbol.asyncIteratorвозвращает Promise<{value, done}>. Работает сfor await...of. Используется в Node.js Readable streams и fetch body