Колекція – це структура даних, котра містить елементи. Наприклад, рядок – це набір символів, а масив – набір впорядкованих елементів:
const message = 'ok'; // містить символи 'o' та 'k'
const numbers = [1, 5, 10]; // містить числа 1, 5, 10Для доступу до елементів колекцій з різною структурою даних, в JavaScript реалізовано спеціальний шаблон (pattern) ітератор, котрий дає змогу обходити елементи колекцій.
Розглянемо приклад, коли є масив, і потрібно вивести у консоль кожний елемент цього масиву:
const numbers = [1, 5, 10];
for (const item of numbers) {
console.log(item);
}
// 1, 5, 10Використовуючи цикл for...of (докладніше про for…of), відбувається вивід у консоль кожного елемента масиву. Це є очікуваною поведінкою.
Тепер, в якості експерименту, спробуємо за допомогою for...of перебрати властивості звичайного JavaScript-об’єкта (докладніше про object):
const person = {
name: 'Virgil',
age: 25,
};
for (const prop of person) {
console.log(prop);
}Результатом виконання прикладу вище стане помилка – TypeError: person is not iterable, котра натякає, що цикл for...of вимагає наявності iterable об’єкту, для перебору його елементів. Тепер слід розібратись, що таке iterable об’єкт.
Інтерфейси Iterable та Iterator
Об’єкт є iterable (тобто його можна перебирати), якщо він відповідає інтерфейсу Iterable, котрий вимагає наявність в об’єкта методу Symbol.iterator, який повинен повертати об’єкт iterator:
interface Iterable {
[Symbol.iterator]() {
//...
return Iterator;
}
}Об’єкт iterator повинен відповідати інтерфейсу Iterator, котрий вимагає наявність в об’єкта методу next(). Метод next повинен повертати об’єкт з властивостями: done (логічне значення, що вказує на кінець ітерації) і value (елемент, отриманий з колекції на поточній ітерації):
interface Iterator {
next() {
//...
return {
value: <value>,
done: <boolean>
};
};
}Iterable об’єкт
В якості прикладу розглянемо масив (array), та визначимо яким чином він відповідає інтерфейсу Iterable. Розглянемо приклад:
const numbers = [1, 5, 10];
console.log(numbers[Symbol.iterator]());
// Object [Array Iterator] {}Виклик виразу numbers[Symbol.iterator]() демонструє, що екземпляр масиву містить спеціальний метод Symbol.iterator, чим підтверджує відповідність масиву інтерфейсу Iterable.
numbers[Symbol.iterator]() повертає об’єкт Iterator, котрий дає змогу виконувати ітерацію над елементами масиву. Для доступу до кожного елемента масиву, достатньо викликати iterator.next():
const numbers = [1, 5, 10];
iterator = numbers[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: 10, done: false }Кожний виклик iterator.next() повертає об’єкт: { value: <item>, done: <boolean> }. Властивість value містить елемент ітерації, властивість done вказує чи завершено ітерацію.
Якщо елементів для ітерації більше немає, iterator.next() повертає { value: undefined, done: true }.
Споживачі iterable об’єктів
JavaScript надає набір інструментів для роботи з об’єктами iterable (цикли, синтаксичні конструкції, функції).
Цикл for…of
Цикл for...of приймає iterable об’єкт, та перебирає його елементи:
const text = 'ok';
for (const char of text) {
console.log(char);
}
// o, kSpread оператор
Ще одним споживачем iterable об’єктів, є spread оператор [...iterable]:
const text = 'hello';
const chars = [...text];
console.log(chars);
// [ 'h', 'e', 'l', 'l', 'o' ]В прикладі вище, spread оператор [...text] перебирає символи рядка, і створює масив з цих символів.
Деструктор масиву
Синтаксис деструкції масивів, також може робити деструкцію iterable об’єктів:
const text = 'hello';
const [firstChar, ...restChars] = text;
console.log(firstChar); // h
console.log(restChars); // [ 'e', 'l', 'l', 'o' ]const [firstChar, ...restChars] = text – присвоєння, де змінній firstChar присвоюється перший символ рядка ‘h’, а решта символів text зберігаються у масив restChars.
Array.from()
Array.from(iterable[, mapFunction]) приймає iterable об’єкт, і повертає масив з його елементів:
const text = 'hello';
const chars = Array.from(text);
console.log(chars);
// [ 'h', 'e', 'l', 'l', 'o' ]Iterable типи у JavaScript
Розглянемо деякі популярні iterable типи даних у JavaScript, окрім вже розглянутих array та string.
Map
Об’єкт Map є iterable, та дає змогу перебирати пари з ключів та значень:
const person = new Map();
person.set('name', 'Marcus');
person.set('age', 28);
for (const [key, value] of person) {
console.log(`${key}: ${value}`);
}
// name: Marcus
// age: 28Set
Об’єкт Set є iterable, та дає змогу перебирати його елементи:
const letters = new Set(['a', 'b', 'c']);
for (const item of letters) {
console.log(item);
}
// a, b, cПідсумок
Iterable – це об’єкт колекція, котрий можна перебирати. Щоб бути iterable, об’єкт повинен відповідати інтерфейсу Iterable.
JavaScript має вбудований набір споживачів для роботи з iterable об’єктами, наприклад – цикл for...of, spread оператор, деструктор масивів, Array.from(). Корисність шаблону iterator полягає в тому, що будь-який iterable об’єкт, може бути використаний будь-яким споживачем iterable об’єктів.
Також є можливість визначати власні iterable типи та споживачі iterable об’єктів, що буде розглянуто в окремому записі.