前端设计模式 —— 发布订阅模式

92 阅读3分钟

发布订阅模式是一种设计模式,它是一种解耦合的方式,用于处理对象之间的通信。在这种模式中,一个对象(发布者)将事件发送到一个中心点,然后订阅者从中心点订阅它们感兴趣的事件,以便在事件发生时得到通知。

在发布订阅模式中,发布者和订阅者是完全解耦合的。发布者不需要知道哪些订阅者存在,而订阅者也不需要知道哪些发布者存在。这种模式提供了一种非常灵活和可扩展的方式来组织代码,并且可以非常容易地添加新的发布者和订阅者。

在 JavaScript 中,常见的应用场景包括事件驱动编程、异步编程、组件通信等。比如在浏览器中,可以通过 addEventListener() 方法订阅一个事件,然后在事件触发时得到通知。在 Node.js 中,可以使用 EventEmitter 类来实现发布订阅模式。

TypeScript 实现

type Subscriber<T> = (data: T) => void;

interface Subscription<T> {
  subscriber: Subscriber<T>;
  once: boolean;
}

class EventEmitter<T> {
  private subscribers: Record<string, Subscription<T>[]> = {};

  public on(event: string, subscriber: Subscriber<T>, once = false): void {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }

    this.subscribers[event].push({ subscriber, once });
  }

  public once(event: string, subscriber: Subscriber<T>): void {
    this.on(event, subscriber, true);
  }

  public off(event: string, subscriber?: Subscriber<T>): void {
    if (!this.subscribers[event]) {
      return;
    }

    if (!subscriber) {
      delete this.subscribers[event];
      return;
    }

    this.subscribers[event] = this.subscribers[event].filter(
      ({ subscriber: s }) => s !== subscriber
    );
  }

  public emit(event: string, data?: T): void {
    if (!this.subscribers[event]) {
      return;
    }

    this.subscribers[event].forEach(({ subscriber, once }) => {
      subscriber(data);

      if (once) {
        this.off(event, subscriber);
      }
    });
  }
}

这个实现定义了 EventEmitter 类,它有 ononceoffemit 四个方法,分别用于订阅事件、订阅一次性事件、取消订阅事件和触发事件。其中:

  • on(event, subscriber, once) 方法用于订阅事件。event 参数是事件名称,subscriber 参数是订阅者回调函数,once 参数表示是否订阅一次性事件。如果该事件不存在订阅者列表中,则创建一个新的订阅者列表,并添加一个新的订阅者到列表中。
  • once(event, subscriber) 方法用于订阅一次性事件。它是 on 方法的简化版,只订阅一次性事件,等价于 on(event, subscriber, true)
  • off(event, subscriber) 方法用于取消订阅事件。如果 subscriber 参数未指定,则删除该事件的所有订阅者;否则,删除指定订阅者。
  • emit(event, data) 方法用于触发事件。它会遍历该事件的订阅者列表,并依次调用订阅者回调函数。如果该事件是一次性事件,则在调用完订阅者回调函数后,删除该订阅者。

使用该实现可以创建一个事件发射器对象,并订阅和触发事件。例如:

const emitter = new EventEmitter<number>();

emitter.on('increment', (count) => {
  console.log(`Count is ${count}`);
});

emitter.emit('increment', 1); // Count is 1
emitter.emit('increment', 2); // Count is 2

该示例创建了一个 EventEmitter 对象 emitter,并订阅了 increment 事件。每次调用 emit 方法时,increment 事件的订阅者会被依次调用。

JavaScript 实现

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

  on(eventName, listener) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(listener);
  }

  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach((listener) => listener(...args));
    }
  }

  off(eventName, listener) {
    if (!this.events[eventName]) {
      return;
    }
    const index = this.events[eventName].indexOf(listener);
    if (index !== -1) {
      this.events[eventName].splice(index, 1);
    }
  }
  
  once(eventName, listener) { 
    const onceWrapper = (...args) => { 
      listener.apply(this, args); 
      this.off(eventName, onceWrapper); 
    }; 
    this.on(eventName, onceWrapper); 
  }
}