Ітератор
Ітератор (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 }.
Швидкий приклад
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
Коли ви пишете:
for (const item of ['a', 'b', 'c']) {
console.log(item);
}під капотом 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), якщо в нього є метод:
obj[Symbol.iterator]()який повертає ітератор. Symbol.iterator, це спеціальний символ, що визначає «ітерабельність» об'єкта, і JavaScript викликає цей метод автоматично, коли треба перебрати об'єкт.
Приклади вбудованих ітерованих структур:
ArrayStringMapSetargumentsNodeList(у браузері)TypedArray
Власний iterable пишеться так:
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, причому він перебирається за кодовими точками, а не за байтами:
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
Усі ці конструкції внутрішньо використовують ітератор:
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*), це функція, яка сама створює ітератор.
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 }А також:
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повісить вкладку.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.