Skip to content

发布订阅

js
class EventEmitter {
  // 使用Map对象来存储事件及其对应的监听器函数
  events = new Map();

  /**
   * 绑定事件到指定监听器函数。
   * 如果监听器不是一个函数,则抛出类型错误。
   * @param {string} event - 事件名称。
   * @param {Function} listener - 监听器函数。
   */
  on(event, listener) {
    if (typeof listener !== 'function') {
      throw new TypeError('Listener must be a function.');
    }
    if (!this.events.has(event)) {
      this.events.set(event, new Set());
    }
    this.events.get(event).add(listener);
  }

  /**
   * 移除指定事件的指定监听器函数。
   * 如果事件不存在,则发出警告。
   * @param {string} event - 事件名称。
   * @param {Function} listener - 要移除的监听器函数。
   */
  off(event, listener) {
    if (!this.events.has(event)) {
      console.warn(`Event "${event}" does not exist.`);
      return;
    }
    const listeners = this.events.get(event);
    listeners.delete(listener);
    if (listeners.size === 0) {
      this.events.delete(event);
    }
  }

  /**
   * 移除所有事件的所有监听器。
   */
  offAll() {
    this.events.clear();
  }

  /**
   * 触发指定事件,并将参数传递给监听器函数。
   * 如果没有监听器被调用,则返回false。
   * 当调用监听器函数时,使用try-catch来防止错误影响其他监听器。
   * @param {string} event - 事件名称。
   * @param {*} args - 传递给监听器函数的参数列表。
   * @return {boolean} 事件是否被触发。
   */
  emit(event, ...args) {
    if (!this.events.has(event)) {
      return false;
    }
    const listeners = this.events.get(event);
    for (const listener of listeners) {
      try {
        listener(...args);
      } catch (error) {
        console.error(`Error occurred when executing listener for event "${event}":`, error);
      }
    }
    return true;
  }

  /**
   * 绑定事件到一个只会被触发一次的监听器。
   * 监听器触发一次后会自动被移除。
   * @param {string} event - 事件名称。
   * @param {Function} listener - 监听器函数。
   */
  once(event, listener) {
    // 一次性包装函数
    const onceWrapper = (...args) => {
      listener(...args);          // 调用原始监听器
      this.off(event, onceWrapper); // 移除包装函数
    };
    this.on(event, onceWrapper); // 绑定包装函数作为监听器
  }
}

export default new EventEmitter();

使用示例

javascript
// 订阅 
Event.on("test-method", val => {
 // ...
 })

// 发布
Event.emit("test-method", false)

重新理解 JavaScript 发布订阅模式