发布订阅模式

发布订阅模式和观察者模式是两种常见的设计模式,用于处理事件和通信。在本文中,我们将逐步构建一个功能完善的EventEmitter,并通过这一过程来深入理解发布订阅模式。

简洁的版本

我们构建一个简单的发布订阅模型:

class EventEmitter {
  constructor() {
    this.events = {};
  }

  // 订阅事件
  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  // 卸载事件
  off(event, listener) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
  }

  // 发布事件
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(listener => listener(data));
    }
  }
}

我们创建了一个EventEmitter类,其中包括了onoffemit方法,分别用于订阅事件、卸载事件和发布事件。

代码流程解释:

  • on(event, listener): 该函数用于订阅事件。如果事件列表中不存在特定事件,我们会创建一个新的事件数组,然后将传入的监听器添加到该数组中。
  • off(event, listener): 这个函数用于卸载事件。我们首先检查事件列表是否包含特定事件,如果是,则使用 filter 方法将传入的监听器从事件数组中移除。
  • emit(event, data): 该函数用于发布事件。如果事件列表包含特定事件,我们将遍历事件数组并触发每个监听器,传递事件数据。

升级版:添加一次性调用

我们引入了一次性订阅,也就是once方法。

// ...

  // 一次性订阅
  once(event, listener) {
    const onceWrapper = data => {
      listener(data);
      this.off(event, onceWrapper);
    };
    this.on(event, onceWrapper);
  }

流程解释:

  • once(event, listener): 这个函数允许你一次性订阅事件,即事件触发一次后自动卸载订阅。它内部创建了一个包装函数 onceWrapper,该函数会在事件触发时执行一次性监听器,然后自动卸载订阅,以确保监听器只会执行一次。

进阶版:支持连续调用

让我们的EventEmitter支持连续调用,这样我们可以在一个链中连续执行多个方法。

class EventEmitter {
  // ...

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

  // 卸载事件,支持连续调用
  off(event, listener) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
    return this;
  }

  // 一次性订阅,支持连续调用
  once(event, listener) {
    const onceWrapper = data => {
      listener(data);
      this.off(event, onceWrapper);
    };
    this.on(event, onceWrapper);
    return this;
  }
}

流程解释:

  • on(event, listener), off(event, listener), once(event, listener): 在这一步,我们添加了 return this; 语句,这意味着每个方法调用后都会返回EventEmitter实例。这允许我们在一个链式调用中连续调用不同的方法,从而提高了代码的可读性。

这三个函数的返回使得我们可以像下面这样连续调用它们:

eventEmitter
  .on('event1', listener1)
  .on('event2', listener2)
  .once('event3', listener3)
  .off('event4', listener4);

通过连续调用,我们可以在一个链中轻松执行多个操作。

完整版:添加事件缓存

在第四步中,我们引入了事件缓存,允许查看已经订阅的事件。

// ...

  // 获取事件列表
  getEvents() {
    return Object.keys(this.events);
  }
}

代码流程解释:

  • getEvents(): 这个函数允许你获取已订阅事件的列表。它返回一个数组,包含了所有已经订阅的事件名称。

发布订阅模式与观察者模式区别

主要区别:

  • 发布订阅模式更加灵活,允许多个发布者和订阅者之间的多对多关系,发布者不需要了解订阅者的存在。它通常使用一个中介来处理事件分发。
  • 观察者模式通常是一对多关系,一个主题对象可以有多个观察者,但观察者的接口是明确定义的。主题对象需要维护观察者列表,并在状态变化时通知观察者。

观察者模式示例:

// 主题
class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }

  notify(message) {
    this.observers.forEach(observer => observer.update(message));
  }
}

// 观察者
class Observer {
  update(message) {
    console.log(`Received message: ${message}`);
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify("Hello, observers!");

在我们上面的介绍中,发布订阅模式使用了中介者来处理事件的发布和订阅,而观察者模式是一对多的关系,主题对象直接通知观察者。

总结

通过逐步构建一个支持连续调用的事件模型,我们介绍了基本的发布订阅模式。同时也简单介绍了发布订阅模式和观察者模式之间差异。发布订阅模式更加灵活,支持多对多关系,适用于更多的场景,而观察者模式是一对多关系,明确定义了观察者的接口。最后希望本文对大家理解发布订阅模式有帮助。


千年老妖
289 声望9 粉丝

10多年开发经验,如何度过35岁危机?