Задача
Реализуйте класс EventEmitter с методами:
on(event, handler)— подписаться;off(event, handler)— отписаться;once(event, handler)— подписаться на один вызов;emit(event, ...args)— вызвать все обработчики события.
Пример
const bus = new EventEmitter();
const onMsg = (text) => console.log('got:', text);
bus.on('message', onMsg);
bus.emit('message', 'hi'); // got: hi
bus.off('message', onMsg);
bus.emit('message', 'bye'); // (ничего)
bus.once('login', (user) => console.log('welcome', user));
bus.emit('login', 'Max'); // welcome Max
bus.emit('login', 'Kate'); // (ничего — once)Решение
Оптимальное решение
class EventEmitter { constructor() { // event -> Set обработчиков (Set даёт O(1) удаление и защиту от дублей) this.events = new Map(); } on(event, handler) { if (!this.events.has(event)) { this.events.set(event, new Set()); } this.events.get(event).add(handler); return () => this.off(event, handler); // удобно вернуть unsubscribe } off(event, handler) { this.events.get(event)?.delete(handler); } once(event, handler) { const wrapper = (...args) => { this.off(event, wrapper); // сначала отписываемся handler(...args); // потом вызываем }; return this.on(event, wrapper); } emit(event, ...args) { // копируем в массив: обработчик может отписаться во время emit const handlers = this.events.get(event); if (!handlers) return; [...handlers].forEach((handler) => handler(...args)); } }
Тонкие места
onceоборачивает обработчик: важно отписаться до вызова, чтобы рекурсивныйemitвнутри не зациклился.- В
emitитерируемся по копии набора — иначе отписка обработчика прямо во время рассылки испортит итерацию.Setвместо массива:offза O(1) и нет дублирующихся подписок одной и той же функции.