Коротка відповідь
middleIterator — це патерн, що дозволяє послідовно перебирати елементи колекції без розкриття її внутрішньої структури. У TS реалізується через інтерфейс `Iterator<T>` з методами `next(): IteratorResult<T>`. Для колекції створюємо клас, що реалізує `Iterable<T>` і повертає інстанс `Iterator`. Це дає можливість використовувати цикл `for…of` або розпакування через `[...iter]`.
Повне пояснення
1. Що це і навіщо
Iterator – патерн перебору колекцій, розміщений у GoF як «Behavioral». Він дозволяє абстрагувати доступ до елементів без розкриття структури колекції, що корисно при роботі з різними типами даних (масиви, списки, графи).
2. Принцип роботи / алгоритм
Патерн передбачає два ключові ролі: Aggregate (колекція) і Iterator. Aggregate має метод createIterator() або реалізує протокол Iterable. Iterator містить стан (поточний індекс) і методи next()/hasNext(). Діаграма: Aggregate → Iterator → Element.
3. Реалізація (JS / TS)
- Базова реалізація
interface Iterator<T> {
next(): IteratorResult<T>;
}
class ArrayIterator<T> implements Iterator<T> {
private index = 0;
constructor(private items: T[]) {}
next() {
if (this.index < this.items.length) {
return { value: this.items[this.index++], done: false };
}
return { value: undefined, done: true };
}
}
- Реалістичний приклад – колекція користувачів у бекенді
class UserCollection implements Iterable<User> {
private users: User[] = [];
add(user: User) { this.users.push(user); }
[Symbol.iterator](): Iterator<User> {
return new ArrayIterator(this.users);
}
}
const users = new UserCollection();
for (const u of users) console.log(u.name);
- Спрощений для новачка – простий масив
const arr = [1, 2, 3];
for (const val of arr) console.log(val);
4. Тестування (Jest або Vitest)
test('ArrayIterator iterates correctly', () => {
const it = new ArrayIterator([10, 20]);
expect(it.next()).toEqual({ value: 10, done: false });
expect(it.next()).toEqual({ value: 20, done: false });
expect(it.next()).toEqual({ value: undefined, done: true });
});
5. Проблеми та edge cases
- Неправильне використання ролей – виклик
next()без перевіркиdoneпризводить до помилок. - Зайва абстракція – створення Iterator для простого масиву збільшує складність без вигоди.
- Cyclical dependencies – колекція, що посилається на Iterator, який знову посилається назад, створює цикл.
- Плутанина між патернами – Iterator vs Generator: обидва ітерують, але генератори мають lazy evaluation.
- Performance‑пастки – створення нового Iterator при кожному
for…ofможе бути дорогим для великих колекцій.
6. Оптимізація та продуктивність
Iterator сам по собі не додає значних накладних витрат; він просто обгортає доступ до елементів. Проте, якщо колекція велика і створюється новий Iterator кожен раз, це збільшує GC‑нагрузку. Використання Symbol.iterator дозволяє браузерам оптимізувати цикл.
7. Без JS / архітектурні альтернативи
У чистій HTML можна ітерувати елементи DOM через NodeList або HTMLCollection, що вже реалізують протокол ітерації. У CSS можна використовувати :nth-child для послідовного доступу, а у ARIA – атрибути aria-posinset/aria-setsize для індексації.
8. Cheatsheet + follow-up
- Cheatsheet:
Iterable<T>→[Symbol.iterator](): Iterator<T>.Iterator<T>→{ next(): IteratorResult<T> }. - Interview questions:
- Як реалізувати власний Iterator для структури даних, що не підтримує
for…of? - Які переваги і недоліки використання Iterator порівняно з Generator?
- Як реалізувати власний Iterator для структури даних, що не підтримує