Джедайские прийоми на JavaScript: магічні властивості транслятора подій


Про що ми?

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

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *