Свои события или observer на Javascript
В этой статье, я по сути "убил двух зайцев" - Написал интересный и полезный Javascript объект, а так же пояснил, как реализовать и где применить идею шаблона Observer в Javascript
Недавно мне в одном проекте понадобилось реализовать собственное событие. Собственное - это событие не стандартного типа: не MouseEvent, не KeyboardEvent, а конкретно своё - допустим, какое нибудь divCollision, происходящее тогда, когда я этого захочу. Т.е. что бы я, скажем, повесил определённое событие, на определённый элемент, и в определённый мною - момент, это событие сработало.
Я не стал шаманить со стандартными возможностями событий, так как события - это один из самых известных геморроев в кроссбраузерной разработке. Мне же нужно было универсальное решение, не зависящее от браузера, и по возможности имеющее не 20++ Кб в объёме.
Поразмыслив, я вспомнил про чудесную вещь - шаблоны программирования... точнее про один из них: Observer - наблюдатель. Он как нельзя лучше подходит в моём случае.
Шаблон Observer
Напомню идею шаблона Observer: есть некий объект, который предоставляет другим объектам - наблюдателям возможность подписаться на определённые события. Ещё как правило шаблон Observer предоставляет методы, что бы от этих событий можно было и отписаться, а так же очень часто Observer реализуется совместно с шаблоном "Одиночка" - Singleton, но речь сейчас не о нём.
Как работает шаблон Observer
Это как очередь на квартиру: в один прекрасный момент вы подаёте заявление на расширение жил. площади, в свой местный жилищный отдел, заранее думая о том, как вы с размахом отметете новоселье, а спустя какое то время (хренову кучу лет), происходит событие! - Вы получаете свою долгожданную квартиру и... естественно устраиваете бурное новоселье!
В описанном выше примере: ваш жил. комитет - это наблюдаемый объект, вы сами и люди подобные вам, тоже стоящие в очереди на жильё - это объекты наблюдатели, а ваше, задуманное новоселье - это функция (метод), которая должна выполнится по наступлению события на которое вы подписаны - получения квартиры. Надеюсь идея ясна.
Реализация шаблона Observer на Javascript
Теперь нам осталось воплотить всё это на Javascript. Итак, нам нужно:
объект, в котором мы будем хранить подписчиков.
Метод, который позволит нам подписываться на события.
Метод, который позволит нам отписываться от событий.
Метод, который позволит нам генерировать события.
- Объемлющий объект, в который мы всё это засунем, что бы всё было в одном месте, и не пересекалось с глобальной областью видимости.
Создадим объект c заготовками свойств и методов:
var observerable = { // Это свойство - объект, будет содержать свойства - имена событий, // которые в свою очередь будут массивами содержащими ссылки // на callback функции подписчиков: listeners : {}, // Добавить подписчика: // object: Object - объект-подписчик; // evt: String - событие для объекта - подписчика; // callback: String - callback имя функции, которая должна выполнится при // возникновении события. Должна быть методом подписчика. addListener : function(/* Object */object, /* String */evt, /* String */callback){}, // Удалить подписчика (аргументы такие же как и у addListener) removeListener : function(/* Object */object, /* String */evt, /* String */callback){}, // Вызвать событие: // evt : String - имя события, // args : Mixed - аргументы, которые мы можем передать в // callback функцию при её срабатывании: triggerEvent : function(/* String */evt, /* Mixed */args){} };
Н... да, если бы в Javascript были абстрактные объекты, то мы только что создали бы один из них- Шутка. Здесь мы определили, так сказать интерфейс наблюдаемого объекта. Но в таком виде мы его конечно не оставим. Далее будем реализоввывать обозначенные методы:
Метод addListener - добавить слушателя ( Наблюдателя ):
... addListener : function(/* Object */object, /* String */evt, /* String */callback) { // Если событие фигурирует впервые: if ( !this.listeners.hasOwnProperty(evt) ) { // Нужно создать для него свойство в объекте this.listeners // заодно сразу обозначим тип данных - массив: this.listeners[evt] = []; } // Помещаем ссылку на метод объекта - подписчика в массив события: // Далее мы сможем запускать код по этой ссылке, даже не передавая // в метод сам оббъект. this.listeners[evt].push(object[callback]); }, ...
Метод removeListener - удалить слушателя ( Наблюдателя ):
// Удалить подписчика (аргументы такие же как и у addListener) ... removeListener : function(/* Object */object, /* String */evt, /* String */callback) { // Проверяем есть ли вообще такое событие в наличии: if ( this.listeners.hasOwnProperty(evt) ) { var i,length; // Здесь просто проходим циклом по свойствам - событиям for (i = 0, length = this.listeners[evt].length; i < length; i += 1) { // Сравниваем значения... if ( this.listeners[evt][i] === object[callback]) { // Если совпало - удаляем элемент из массива: this.listeners[evt].splice(i, 1); } } } }, ...
Метод triggerEvent сгенерировать событие
... triggerEvent : function(/* String */evt, /* Mixed */args) { // Проверяем есть ли вообще такое событие в наличии: if ( this.listeners.hasOwnProperty(evt) ) { var i,length; // Проходимся по всем подписчикам: for (i = 0, length = this.listeners[evt].length; i < length; i += 1) { // Вызываем их callback - функции, передавая им аргументы, если они есть: this.listeners[evt][i](args); } } }, ...
Полный листинг объекта, реализующего шаблон Observer
// Листинг без комментариев var observerable = { listeners : {}, addListener : function(object, evt, callback) { if ( !this.listeners.hasOwnProperty(evt) ) { this.listeners[evt] = []; } this.listeners[evt].push(object[callback]); }, removeListener : function(object, evt, callback) { if ( this.listeners.hasOwnProperty(evt) ) { var i,length; for (i = 0, length = this.listeners[evt].length; i < length; i += 1) { if( this.listeners[evt][i] === object[callback]) { this.listeners[evt].splice(i, 1); } } } }, triggerEvent : function(evt, args) { if ( this.listeners.hasOwnProperty(evt) ) { var i,length; for (i = 0, length = this.listeners[evt].length; i < length; i += 1) { this.listeners[evt][i](args); } } } };
Использование объекта
Давайте создадим парочку наблюдателей и зарегистрируем их в нашем наблюдаемом объекте:
var one = { callBackOne : function(e) { alert("Вызван подписчик ONE: " + e.message); } }; var two = { callBackTwo : function(e) { alert("Вызван подписчик TWO: " + e.message); } }; // Регистрируем наблюдателей для событий someEventForOne и someEventForTwo: observerable.addListener( one, "someEventForOne", "callBackOne"); observerable.addListener( two, "someEventForTwo", "callBackTwo"); // Теперь допустим, что то произошло и мы в коде вызвали события // someEventForOne и someEventForTwo, а заодно передаём // какую нибудь полезную информацию : observerable.triggerEvent("someEventForOne", {message : "i am one event"}); observerable.triggerEvent("someEventForTwo", {message : "i am two event"});
Если вы всё сделали правильно, то вы получите два окна с сообщениями: "Вызван подписчик ONE: i am one event" и "Вызван подписчик TWO: i am two event" что свидетельствует о том, что события сработали. Теперь проверим удаление наблюдателя добавим после регистрации наблюдателей и до вызова событий строчку:
observerable.removeListener(one, "someEventForOne", "callBackOne");
И мы увидим только одно диалоговое окно: "Вызван подписчик TWO: i am two event" - удаление наблюдателя сработало.
Теперь пара слов о том что мы тут натворили. Мы реализовали идею шаблона Observer - наблюдатель таким образом, что мы можем придумывать свои события, подписывать на каждое событие сколь угодно объектов, вызывать эти события, когда мы захотим, а так же отписывать наблюдателей. Мы имеем весь код в одном объекте, и глобальная облать видимости пополнилась лишь одной переменной: observerable
Информация копипастерам
Внимание! Копирование контента с сайта, возможно только с разрешения администратора. Т.е. Меня! Я скорее всего разрешу Вам это сделать, в обмен на живую ссылку, на статью оригинал.