О чем мы?
Event Emitter можно перевести как «транслятор» или «эмиттер» событий. Звучит как название штуки, умеющей генерировать событие, которое может «услышать» кто угодно.
Представьте себе такую схему: в вашем асинхронном коде определенный участок может «крикнуть» остальным, что он выполнил свою задачу, а другие части «услышат» этот сигнал и примут соответствующие меры.
Event Emitter — это шаблон, который можно реализовать разными способами. Основная идея в том, чтобы грамотно создать основу для управления событиями и реализовать возможность любым элементам «подписаться» на него (и быть в курсе происходящего). С другими шаблонами проектирования вы можете познакомиться в нашей статье.
Уже интересно, как такая магия может работать? Итак, мы хотим добиться кода, который затем можно будет использовать так:
let input = document.querySelector('input[type="text"]');
let button = document.querySelector('button');
let h1 = document.querySelector('h1');
button.addEventListener('click', () => {
emitter.emit('event:name-changed', {name: input.value});
});
let emitter = new EventEmitter();
emitter.subscribe('event:name-changed', data => {
h1.innerHTML = `Your name is: ${data.name}`;
});
Начнем.
Реализация
class EventEmitter {
constructor() {
this.events = {};
}
}
Как видите, конструктор нашего класса будет инициализировать поле events, пока что делая его пустым объектом. Задача этого поля — хранить события, «подписавшиеся» на нас (то есть в нем будут храниться функции).
Метод subscribe
:
subscribe( eventName, fn ) {
if( !this.events[eventName] ) {
this.events[eventName] = [];
}
this.events[eventName].push(fn);
}
Этот метод принимает в качестве аргументов название события (например, event:name-changed
, как в нашем примере) и функцию, которая будет вызываться, когда будет инициироваться транслируемое событие.
Одна из ключевых особенностей функций в JavaScript состоит в том, что функции — это этакие «объекты первого класса», то есть мы можем передать функцию в качестве параметра другой функции, как в методе subscribe()
.
Метод emit
:
emit(eventName, data) {
const event = this.events[eventName];
if( event ) {
event.forEach(fn => {
fn.call(null, data);
});
}
}
Этот метод принимает имя события, которое мы хотим всем транслировать, и данные, которые будут отправляться в момент этого события. Если в экземпляре класса сохранены какие-то подписанные на него события, мы проходимся по каждому из них и вызываем каждое, передавая ему данные, которые хотим транслировать.
Собственно, с реализацией паттерна мы закончили. Но пока остается одна проблема: нам нужно будет «отписать» функции, которые нам больше не нужны. Не сделаем этого — столкнемся с утечкой памяти.
Давайте решим эту проблему. Пусть метод subscribe()
возвращает функцию unsubscribe()
, которую позже можно будет использовать, чтобы отписаться от события.
subscribe(eventName, fn) {
if(!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(fn);
return () => {
this.events[eventName] = this.events[eventName].filter(eventFn => fn !== eventFn);
}
}
Как мы помним, в JavaScript особенные функции — их легко можно вернуть из другой функции. Теперь метод subscribe()
можно использовать следующим образом:
let unsubscribe = emitter.subscribe('event:name-changed', data => console.log(data));
unsubscribe();
Вызывая сохраненную в переменной функцию unsubscribe()
, мы отписываемся от события.
Вот и все. Пока-пока, утечки памяти!
Вот тут можно попробовать весь получившийся код в действии.
Источник: Medium