Iterator
Итератор — паттерн, который дает последовательный доступ к элементам коллекции, не раскрывая её внутреннего устройства (будь то массив, связный список или дерево).
Обход коллекции
graph LR
Collection["Collection (Array/Set/Map)"] -- "getIterator()" --> Iterator
Iterator -- ".next()" --> Item1[Item 1]
Iterator -- ".next()" --> Item2[Item 2]
Iterator -- ".next()" --> Done[Done: true]
В Java или C++ вы часто создаете итераторы вручную. В JavaScript этот паттерн встроен в сам язык на уровне ядра. Всякий раз, когда вы пишете цикл for..of или используете спред-оператор ..., вы неявно используете Итератор.
Генераторы и Протоколы
В JS есть специальный символ Symbol.iterator. Если у объекта есть этот метод, он становится "перебираемым". Самый мощный инструмент здесь — Генераторы (функции со звездочкой function*).
// Функция-генератор (Iterator Pattern "из коробки")
function* idGenerator() {
let id = 1;
while (true) {
yield id++; // Возвращает значение и "замораживает" выполнение
}
}
const iterator = idGenerator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
Архитектурная справка
Раньше в вебе правил паттерн MVC. Контроллер менял Модель, Модель обновляла Вид, Вид мог дернуть Контроллер. В больших приложениях стрелки запутывались, и никто не понимал, почему данные изменились.
Facebook (Meta) предложил решение, основанное на паттернах Command и Observer, назвав его Flux (а позже — Redux). Главное правило: Данные текут только в одну сторону.
graph LR
Action["Action (Command)"] --> Dispatcher
Dispatcher --> Store["Store (Model)"]
Store -- "Notify (Observer)" --> View[React View]
View -- "User Click" --> Action
Command
Команда — превращает запрос (действие) в объект. Этот объект содержит всё необходимое для выполнения действия: имя метода, параметры и данные.
Инкапсуляция действия
flowchart LR
User((User)) -- "Clicks Button" --> UI[UI Component]
UI -- "Creates" --> Action["Command Object\n{ type: 'ADD_TODO', text: 'Buy Milk' }"]
Action -- "Dispatched to" --> Store[Store / System]
Store -- "Executes" --> Update[Update State]
В классическом ООП выделяют 4 роли: Command, Receiver, Invoker, Client. Звучит сложно? Давайте посмотрим на это глазами Frontend-разработчика.
Redux Actions
Вы учили Redux и вы уже знаете этот паттерн. Redux Action — это и есть Команда.
Вместо того чтобы напрямую менять стейт state.count++ (императивный подход), мы создаем объект-команду { type: 'INCREMENT' } и отправляем её диспетчеру. Это позволяет:
- Логировать все действия Redux DevTools.
- Делать отмену действий, просто удаляя последнюю команду из истории.
- Передавать команды по сети.
// 1. Command (Команда)
const addToCart = (product) => ({
type: 'ADD_TO_CART',
payload: product
});
// 2. Invoker (Тот, кто вызывает)
const dispatch = (action) => {
console.log('Executing command:', action.type);
// logic...
};
// 3. Client (UI)
button.onClick = () => dispatch(addToCart({ id: 1, name: 'Phone' }));
Observer
Наблюдатель — механизм подписки. Один объект (Субъект) меняет состояние, и все, кто на него подписался (Наблюдатели), автоматически получают уведомление.
One-to-Many Dependency
sequenceDiagram
participant Subject as YouTube Channel
participant Obs1 as Subscriber 1
participant Obs2 as Subscriber 2
Note over Subject: New Video Uploaded!
Subject->>Obs1: Notify("New Video!")
Subject->>Obs2: Notify("New Video!")
Obs1->>Obs1: Watch Video
Obs2->>Obs2: Watch Video
Весь браузер построен на этом паттерне. Метод addEventListener — это чистейшая реализация Наблюдателя. Вы подписываетесь на клики, движения мыши или загрузку страницы.
RxJS и React Effects
- DOM Events:
button.addEventListener('click', handler). - React useEffect: вы "наблюдаете" за переменными в массиве зависимостей.
[userId]изменился -> эффект сработал. - RxJS: библиотека, которая возводит этот паттерн в абсолют. Всё есть поток данных, на который можно подписаться.
// Простейший Observable на JS
class NewsChannel {
constructor() {
this.subscribers = [];
}
subscribe(callback) {
this.subscribers.push(callback);
}
broadcast(news) {
this.subscribers.forEach(sub => sub(news));
}
}
const channel = new NewsChannel();
channel.subscribe(news => console.log('Subscriber 1 read:', news));
channel.broadcast('Breaking News: JS is awesome!');
Visitor
Посетитель — позволяет добавить новую операцию для целой структуры объектов, не меняя классы этих объектов. Это "гость", который ходит по домам и делает работу, которую хозяева делать не умеют.
Обход дерева структур
graph TD
Visitor((Visitor))
Root[Root Node]
ChildA[Node A]
ChildB[Node B]
Root --> ChildA
Root --> ChildB
Visitor -.->|Visits| Root
Visitor -.->|Visits| ChildA
Visitor -.->|Visits| ChildB
В повседневной UI-разработке встречается редко. Но это ключевой паттерн для инструментов разработчика (Babel, ESLint, Prettier).
Babel и AST
Ваш код — это дерево (AST - Abstract Syntax Tree). Babel использует паттерн Visitor, чтобы пройти по этому дереву и превратить современный JS в старый (транспиляция).
Пример: посетитель находит все узлы "VariableDeclaration" с типом "const" и меняет их на "var". Сам узел кода не знает, как себя транспилировать, это делает внешний Visitor.
Mediator
Посредник — объект, который скрывает сложные связи между объектами. Вместо того чтобы компоненты общались друг с другом напрямую (создавая паутину зависимостей), они общаются только с Посредником.
Централизация хаоса
graph TD
subgraph Chaos ["Без Посредника"]
A1((A)) <--> B1((B))
A1 <--> C1((C))
B1 <--> C1
end
subgraph Order ["С Посредником"]
A2((A)) <--> M[Mediator]
B2((B)) <--> M
C2((C)) <--> M
end
Главное отличие от Observer: В Observer "Субъект" просто рассылает уведомления и ему всё равно, кто слушает. В Mediator центральный объект содержит бизнес-логику принятия решений.
Сложные формы и чаты
Представьте форму регистрации с полями: "Страна", "Город", "Индекс".
- Выбор "Страны" должен очистить "Город".
- Выбор "Города" должен подсказать "Индекс".
Если поля будут управлять друг другом напрямую — получится спагетти-код. Лучше создать хук useFormMediator, который знает про все поля и управляет зависимостями.
// Mediator (Координатор чата)
class ChatRoom {
showMessage(user, message) {
const time = new Date().toLocaleTimeString();
const sender = user.getName();
console.log(`${time} [${sender}]: ${message}`);
// Здесь могла быть логика фильтрации мата или отправки на сервер
}
}
class User {
constructor(name, chatMediator) {
this.name = name;
this.chat = chatMediator;
}
getName() { return this.name; }
send(message) {
// User не знает, как отправить сообщение, он просит посредника
this.chat.showMessage(this, message);
}
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ