实现观察者模式

手写 JavaScript 观察者模式

问题

实现观察者模式(发布-订阅模式),支持订阅、取消订阅和发布事件。

解答

基础实现

class EventEmitter {
  constructor() {
    // 存储事件和对应的回调函数列表
    this.events = {};
  }

  // 订阅事件
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return this; // 支持链式调用
  }

  // 取消订阅
  off(event, callback) {
    if (!this.events[event]) return this;
    
    if (!callback) {
      // 没传回调则移除该事件所有监听
      delete this.events[event];
    } else {
      // 移除指定回调
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
    return this;
  }

  // 发布事件
  emit(event, ...args) {
    if (!this.events[event]) return this;
    
    this.events[event].forEach(callback => {
      callback.apply(this, args);
    });
    return this;
  }

  // 只订阅一次
  once(event, callback) {
    const wrapper = (...args) => {
      callback.apply(this, args);
      this.off(event, wrapper); // 执行后立即移除
    };
    this.on(event, wrapper);
    return this;
  }
}

使用示例

const emitter = new EventEmitter();

// 订阅事件
function handleMessage(data) {
  console.log('收到消息:', data);
}

emitter.on('message', handleMessage);
emitter.on('message', (data) => {
  console.log('另一个监听器:', data);
});

// 发布事件
emitter.emit('message', { text: 'Hello' });
// 输出:
// 收到消息: { text: 'Hello' }
// 另一个监听器: { text: 'Hello' }

// 取消订阅
emitter.off('message', handleMessage);
emitter.emit('message', { text: 'World' });
// 输出:
// 另一个监听器: { text: 'World' }

// 只订阅一次
emitter.once('login', (user) => {
  console.log('用户登录:', user);
});

emitter.emit('login', 'Tom'); // 输出: 用户登录: Tom
emitter.emit('login', 'Jerry'); // 无输出,监听器已移除

关键点

  • 用对象存储事件名到回调数组的映射
  • on 添加回调到数组,off 从数组中移除
  • emit 遍历执行所有回调,用 apply 传递参数
  • once 通过包装函数实现执行后自动移除
  • 返回 this 支持链式调用