Skip to main content

Ітератор

Ітератор (iterator), це об'єкт, який почергово повертає елементи якоїсь послідовності на кожен виклик методу next(). Формально: ітератор, це об'єкт, що реалізує метод next(), який повертає об'єкт вигляду { value: any, done: boolean }.

Теорія

TL;DR

  • Ітератор, це об'єкт із методом next(), що повертає { value, done }.
  • Коли done: true, ітерація завершується, а value тоді зазвичай undefined.
  • Iterable, це об'єкт, у якого є метод [Symbol.iterator](), що повертає ітератор.
  • for...of, spread ..., Array.from() і деструктуризація працюють саме через цей протокол.
  • Вбудовані iterable: Array, String, Map, Set, arguments, NodeList, типізовані масиви.
  • Генератор function* створює ітератор автоматично, без ручного return { value, done }.

Швидкий приклад

javascript
function createIterator(arr) { let i = 0; return { next() { if (i < arr.length) { return { value: arr[i++], done: false }; } else { return { value: undefined, done: true }; } } }; } const iterator = createIterator(['a', 'b', 'c']); 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 }

Коли done: true, ітерація закінчується.

Що робить ітератор усередині for...of

Коли ви пишете:

javascript
for (const item of ['a', 'b', 'c']) { console.log(item); }

під капотом JavaScript робить приблизно таке:

javascript
const iterable = ['a', 'b', 'c']; const iterator = iterable[Symbol.iterator](); // створює ітератор while (true) { const { value, done } = iterator.next(); if (done) break; console.log(value); }

Тобто цикл сам не знає нічого про масиви: він знає лише протокол next().

Iterable і Symbol.iterator

Об'єкт є ітерованим (iterable), якщо в нього є метод:

javascript
obj[Symbol.iterator]()

який повертає ітератор. Symbol.iterator, це спеціальний символ, що визначає «ітерабельність» об'єкта, і JavaScript викликає цей метод автоматично, коли треба перебрати об'єкт.

Приклади вбудованих ітерованих структур:

  • Array
  • String
  • Map
  • Set
  • arguments
  • NodeList (у браузері)
  • TypedArray

Власний iterable пишеться так:

javascript
const range = { from: 1, to: 4, [Symbol.iterator]() { let current = this.from; let last = this.to; return { next() { if (current <= last) { return { value: current++, done: false }; } else { return { done: true }; } } }; } }; for (const num of range) { console.log(num); // 1, 2, 3, 4 }

Коли JavaScript зустрічає for...of, він викликає range[Symbol.iterator]() і використовує повернений ітератор.

Рядок теж є iterable, причому він перебирається за кодовими точками, а не за байтами:

javascript
const str = 'Hi'; const iterator = str[Symbol.iterator](); console.log(iterator.next()); // { value: 'H', done: false } console.log(iterator.next()); // { value: 'i', done: false } console.log(iterator.next()); // { value: undefined, done: true }

Ітератори разом зі spread, деструктуризацією та Array.from

Усі ці конструкції внутрішньо використовують ітератор:

javascript
const set = new Set([1, 2, 3]); console.log([...set]); // [1, 2, 3] console.log(Array.from(set)); // [1, 2, 3] const [a, b] = set; console.log(a, b); // 1 2

Саме тому spread працює з Set і Map, хоча вони не є масивами: їм достатньо мати [Symbol.iterator]().

Генератори, це автоматичні ітератори

Генератор (function*), це функція, яка сама створює ітератор.

javascript
function* numbers() { yield 1; yield 2; yield 3; } const iterator = numbers(); 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 }

А також:

javascript
for (const n of numbers()) { console.log(n); // 1, 2, 3 }

Генератори зручні тим, що не треба вручну писати return { value, done }: рушій формує ці об'єкти сам із кожного yield.

Ітерабельність сучасних структур:

СтруктураІтерована?Повертає
Arrayтакзначення
Stringтаксимволи
Mapтакпари [key, value]
Setтакзначення
Objectнінічого, але можна додати [Symbol.iterator]() вручну
argumentsтакзначення аргументів

Короткий підсумок:

ТермінОпис
ІтераторОб'єкт із методом next(), що повертає { value, done }
Ітерований об'єктОб'єкт, у якого є метод [Symbol.iterator]()
Symbol.iteratorСимвол, що вказує на метод, який створює ітератор
Використовується вfor...of, spread, Array.from, деструктуризації
Генератор (function*)Спрощений спосіб створити ітератор

Типові помилки

  • Плутати ітератор з iterable. Ітератор має next(), iterable має [Symbol.iterator](). Масив, це iterable, а arr[Symbol.iterator](), це вже ітератор.
  • Очікувати, що for...of працюватиме зі звичайним об'єктом. У {} немає [Symbol.iterator](), тому буде TypeError. Для об'єктів беруть Object.keys(), Object.values() або Object.entries().
  • Повторно перебирати один і той самий ітератор. Він одноразовий: після { done: true } він назавжди вичерпаний, і другий for...of не дасть нічого.
  • Забувати повертати { value, done } саме як об'єкт. Якщо next() поверне просте значення, рушій кине TypeError.
  • Плутати for...of і for...in. for...in перебирає ключі, включно з успадкованими, і жодного стосунку до протоколу ітерації не має.
  • Писати нескінченний iterable без обмеження. Генератор із while (true) у for...of без break або без take повісить вкладку.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.