Свои события или observer на Javascript

Свои события или observer на Javascript

В этой статье, я по сути "убил двух зайцев" - Написал интересный и полезный Javascript объект, а так же пояснил, как реализовать и где применить идею шаблона Observer в Javascript

Недавно мне в одном проекте понадобилось реализовать собственное событие. Собственное - это событие не стандартного типа: не MouseEvent, не KeyboardEvent, а конкретно своё - допустим, какое нибудь divCollision, происходящее тогда, когда я этого захочу. Т.е. что бы я, скажем, повесил определённое событие, на определённый элемент, и в определённый мною - момент, это событие сработало.

Я не стал шаманить со стандартными возможностями событий, так как события - это один из самых известных геморроев в кроссбраузерной разработке. Мне же нужно было универсальное решение, не зависящее от браузера, и по возможности имеющее не 20++ Кб в объёме.

Поразмыслив, я вспомнил про чудесную вещь - шаблоны программирования... точнее про один из них: Observer - наблюдатель. Он как нельзя лучше подходит в моём случае.

Шаблон Observer

Напомню идею шаблона Observer: есть некий объект, который предоставляет другим объектам - наблюдателям возможность подписаться на определённые события. Ещё как правило шаблон Observer предоставляет методы, что бы от этих событий можно было и отписаться, а так же очень часто Observer реализуется совместно с шаблоном "Одиночка" - Singleton, но речь сейчас не о нём.

Как работает шаблон Observer

Это как очередь на квартиру: в один прекрасный момент вы подаёте заявление на расширение жил. площади, в свой местный жилищный отдел, заранее думая о том, как вы с размахом отметете новоселье, а спустя какое то время (хренову кучу лет), происходит событие! - Вы получаете свою долгожданную квартиру и... естественно устраиваете бурное новоселье!

событие observer

В описанном выше примере: ваш жил. комитет - это наблюдаемый объект, вы сами и люди подобные вам, тоже стоящие в очереди на жильё - это объекты наблюдатели, а ваше, задуманное новоселье - это функция (метод), которая должна выполнится по наступлению события на которое вы подписаны - получения квартиры. Надеюсь идея ясна.

Реализация шаблона Observer на Javascript

Теперь нам осталось воплотить всё это на Javascript. Итак, нам нужно:

  1. объект, в котором мы будем хранить подписчиков.

  2. Метод, который позволит нам подписываться на события.

  3. Метод, который позволит нам отписываться от событий.

  4. Метод, который позволит нам генерировать события.

  5. Объемлющий объект, в который мы всё это засунем, что бы всё было в одном месте, и не пересекалось с глобальной областью видимости.

Создадим объект 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

Добавить комментарий


Защитный код
Обновить



Кто на сайте
Сейчас 68 гостей онлайн