Множественные события для объекта

Подскажите, есть ли в js обработчик множественных событий для объектов? Сейчас реализовано так:

obj = {
    ev: function() {},

    actions: function() {
        ...

        this.ev();
    }
}

... //Здесь создается новый объект типа "obj"
newObj.ev = function() {
    ...
}

В этом случае, если потребуется назначить для ev второй обработчик, то он перезапишет первый. Для DOM есть addEventListener, есть ли что-то подобное для обычных объектов?


Ответы (2 шт):

Автор решения: DiD

Я использую довольно простую реализацию Emitter на манер node.js но для браузера. Просто наследуете класс от него.

/**
 * The Emitter is a simple emitter class that provides you with `on()`, `off()` and `trigger()` methods
 * @class Emitter
 */
export default class Emitter {
  callbacks = new Map();
  context;
  isElement = false;
  constructor(context) {
    this.context = context;
    this.isElement = context instanceof Element;
  }

  /**
   * Registers callbacks by event name
   * @param {String} type
   * @param {Function} callback
   */
  on(type, callback) {
    if (this.isElement) {
      this.context.addEventListener(type, callback);
    }
    if (!this.callbacks.has(type)) {
      this.callbacks.set(type, new Set());
    }

    this.callbacks.get(type).add(callback);

    return this;
  }

  /**
   * Deregister callbacks by event name
   * @param {String} type
   * @param {Function} callback
   */
  off(type, callback) {

    if(!type && !callback){
      for(let type of this.callbacks.keys()){
        if(this.isElement){
          let callbacks = [...this.callbacks.get(type)];
          for(let callback of callbacks){
            this.context.removeEventListener(type, callback);
          }
        }
        this.callbacks.get(type).clear();
      }      
    }

    else if (!this.callbacks.has(type)) {
      return null;
    }

    else if (!callback) {
      if (this.isElement) {
        const callbacks = [...this.callbacks.get(type)];
        for (let callback of callbacks) {
          this.context.removeEventListener(type, callback);
        }
      } else {
        this.callbacks.get(type).clear();
      }
    } else {
      if (!this.callbacks.get(type).has(callback)) {
        return null;
      }

      this.callbacks.get(type).delete(callback);

      if (this.isElement) {
        this.context.removeEventListener(type, callback);
      }
    }

    return this;
  }

  /**
   * Triggers event callbacks by event object
   * @param {AbstractEvent} event
   */
  trigger(event) {
    if (this.isElement) {
      this.context.dispatchEvent(event);
    } else {
      if (!this.callbacks.has(event.type)) {
        return null;
      }

      const callbacks = [...this.callbacks.get(event.type)];
      const caughtErrors = [];

      for (let i = callbacks.length - 1; i >= 0; i--) {
        const callback = callbacks[i];

        try {
          callback(event);
        } catch (error) {
          caughtErrors.push(error);
        }
      }

      if (caughtErrors.length) {
        /* eslint-disable no-console */
        console.error(
          `Emitter caught errors while triggering '${event.type}'`,
          caughtErrors
        );
        /* eslint-disable no-console */
      }
    }

    return this;
  }
  /**
   * trigger alias
   */
  fire(...args){
     this.trigger(...args);
  }
  /**
   * trigger alias
   */
  emit(...args){
     this.trigger(...args);
  }
}
→ Ссылка
Автор решения: nup

Еще один, более простой, вариант реализации с глобальным менеджером событий. Создается событие с определенным именем, например, "death". В любом месте скрипта можно "подписаться" на это событие. При возникновении события должна быть вызвана функция callEvent, которая обойдет и вызовет все "подписанные" на него функции. Определить, кто вызвал событие, можно по объекту target, который передается в функцию.

Очень простая реализация, но в некоторых ситуациях ее вполне может хватить.

events = {
    items: {},

    addEvent: function(event) {
        this.items[event] = [];
    },

    addListener: function(event, func) {
        this.items[event].push(func);
    },

    callEvent: function(event, target) {
        if (typeof this.items[event] == 'undefined') return false;

        this.items[event].forEach(function(value, index) {
            value(target);
        });
    }
}

mainObj1 = {
    id: 'main 1',

    init: function() {
        var self = this;

        events.addEvent('death');

        setTimeout(function() {
            console.log(self.id + ' - инициализировал событие');
            events.callEvent('death', self);
        }, 2000);
    }
}

mainObj2 = {
    id: 'main 2',

    init: function() {
        var self = this;

        events.addEvent('death');

        setTimeout(function() {
            console.log(self.id + ' - инициализировал событие');
            events.callEvent('death', self);
        }, 5000);
    }
}

obj1 = {
    id: 'obj1',

    init: function() {
        var self = this;

        events.addListener('death', function(target) {
            console.log(self.id + ' - получил событие от ' + target.id);
        });
    }
}

obj2 = {
    id: 'obj2',

    init: function() {
        var self = this;

        events.addListener('death', function(target) {
            console.log(self.id + ' - получил событие от ' + target.id);
        });
    }
}

mainObj1.init();
mainObj2.init();
obj1.init();
obj2.init();

https://jsfiddle.net/efsb7Lzy/3/

→ Ссылка