Everybody calls me Lut



Емітери подій

Словом, дали мені таск — написати простий емітер. На вході маємо отаке:

var a = {  
};
function emitter(obj) {  
}
a = emitter(a);  
a.emit(‘eventName’, {1: 2});  
//відбувається івент 'eventName', передаються дані {1: 2}
a.emit(‘eventName’, {1: 2});  
//еміт події
a.off(‘eventName’);  
//всі event listeners вимикаються
a.one(‘eventName’, function (data) {  
});
//event listener який виконується лише раз. Після виконання callback - listener відписується від події 
a.on(‘eventName’, function (data) {  
});
//типовий event listener
a.emit(‘eventName’, {1: 2});  
a.emit(‘eventName’, {1: 2});  
a.off(‘eventName’);  
//вимикаються усі listener`и
a.emit(‘eventName’, {1: 2});  

Завданням було привести даний код до чуття. Подумати, зробити щось, щоби воно заводилося.

Спочатку пару слів про те, що таке емітери і з чим їх їсти. Нехай нам треба повішати якийсь івент на DOM-елемент.

$(body).click(callback());

Задача тривіальна. Що станеться, якщо івент відбудеться до того, як на нього підпишуть event listener? Нічого. Що станеться, якщо таких listener’ів буде декілька? Виконаються усі callback-функції.

Еміт перекладається як “випромінювання, видача”. Суть — написати методи для івенту(emit) і для роботи з його слухачами (on, one, off).

Припустимо, що код працює і ці методи вже створені. Що відбудеться?

a.emit(‘eventName’, {1: 2});  
a.emit(‘eventName’, {1: 2});  
a.off(‘eventName’);  
a.one(‘eventName’, function (data) {  
  console.log("One-time event listener");
});
a.on(‘eventName’, function (data) {  
  console.log("Typical event listener");
});
a.emit(‘eventName’, {1: 2});  
a.emit(‘eventName’, {1: 2});  
a.off(‘eventName’);  
a.emit(‘eventName’, {1: 2});  

Спершу двічі емітяться події eventName з датасетом {1: 2}. Далі a.one підписує event listener на подію eventName, який викличе відповідну коллбек-функцію, після виконання якої listener повинен бути відписаним від івенту (одноразовий івент лісенер). a.on підпише на eventName listener, який буде працювати, аж поки ми не викличемо a.off. Таким чином, коллбеки викличутьсялиш на рядках 10–11 коду. Відповідно, в консолі ми отримаємо:

One-time event listener  
Typical event listener  
Typical event listener  

Ок, поїхали. Очевидно, emitter повинен розширювати об’єкт a певними методами (emit, on, one, off).

Нам потрібна якась модель даних. Виходячи з того, що в нас на один івент може бути навішано багато коллбеків різних типів, найкращою структурою, яку я поки можу уявити буде щось типу

a = {  
  "eventName": [[cb1, true], [cb1, true], [cb2, false], [cb, true]],
  "eventName2": [[cb, true]]
};

Таким чином, кожного разу, коли ми підписуємося на якусь нову подію, об’єкт розширюється полем з назвою цієї події та масивом коллбеків. Другий елемент цього масиву визначає спосіб, яким ми підписалися (одноразово чи постійно). Тепер власне код еммітера:

function emitter(obj) {  
  obj.emit = function(en, data){
    if(this[en]){
      for (var i = 0; i < this[en].length; i++){
        this[en][i][0].apply(this, [data]);
        if (this[en][i][1]){
          delete this[en];
        }
      }
    }
  };
  obj.one = function(en, cb){
    this[en] = this[en] || [];
    this[en].push([cb, true]);
  };
  obj.off = function(en, cb){
    delete this[en]; 
  };
  obj.on = function(en, cb){
    this[en] = this[en] || [];
    this[en].push([cb, false]);
  }
  return obj;
}

Можете почитати тут про емітери. Ну а я поки куплю собі якусь книгу про js паттерни.