Пошук уроків, статей та іншого контенту
Що насправді робить об'єкт «ітерованим» для for...of, і як створити власний ітератор.
У модулі «Цикли» ми використовували for...of для масивів, рядків, Map і Set — але не для звичайних об'єктів:
const obj = { a: 1, b: 2 };
for (const value of obj) {
console.log(value);
}
// TypeError: obj is not iterableРізниця не довільна: масиви, рядки, Map і Set реалізують протокол ітерації (iterable protocol), а звичайний об'єкт — ні. for...of працює лише з тим, що цей протокол реалізує.
Об'єкт вважається ітерованим, якщо має метод із спеціальним ключем Symbol.iterator (Symbol розглянемо детальніше в наступному уроці — це унікальний, недоступний для випадкового перезапису ключ властивості). Цей метод має повертати ітератор — об'єкт із методом next(), що повертає { value, done } на кожен виклик:
const range = {
from: 1,
to: 3,
[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
}Цикл for...of — це, по суті, зручний синтаксис над ручним викликом next(), доки done не стане true:
const iterator = range[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
// те саме, що for (const num of range) { console.log(num); }Оператор spread (модуль «Об'єкти та масиви») і деструктуризація масивів працюють з будь-яким ітерованим об'єктом, не лише з масивами — саме тому [...range] із нашим власним range теж працює:
[...range]; // [1, 2, 3] — spread використовує той самий Symbol.iteratorРозуміння протоколу пояснює, чому одні структури працюють із for...of/spread, а звичайні об'єкти — ні (тому для об'єктів потрібен Object.entries, модуль «Об'єкти та масиви», що повертає масив пар — уже ітерований за визначенням). Це також основа для наступного уроку — генератори є найзручнішим способом написати Symbol.iterator без ручного керування станом current.
Спроба застосувати for...of чи spread до звичайного об'єкта напряму замість Object.entries/keys/values.
Забування повернути { done: true } у власному ітераторі — цикл піде в нескінченне виконання.
Плутанина ітерованого об'єкта (має Symbol.iterator) з самим ітератором (має next()) — це різні, хоч і пов'язані, сутності.
Об'єкт ітерований, якщо реалізує Symbol.iterator, що повертає ітератор із методом next(), який на кожен виклик дає { value, done }. for...of, spread і деструктуризація масивів усі спираються саме на цей протокол — це те, що об'єднує масиви, рядки, Map, Set і будь-яку власну структуру, яка його реалізує.
Спробуйте самостійно
Створіть ітерований об'єкт evenNumbers, який при переборі через for...of видає перші 5 парних чисел (2, 4, 6, 8, 10), реалізувавши Symbol.iterator вручну, як у прикладі вище.