Dark Dwarf Blog background

发布——订阅模式

发布——订阅模式

在实现 TicTacToe 的时候用到了这个,因此记录一下。

1. 概述

发布-订阅模式是一种消息传递范式,其中“发布者”(publisher)发送消息,而无需知道哪些“订阅者”(subscriber)会接收它。这是一种强大的解耦机制。

2. 模式核心

这个模式主要由下面这些内容组成:

  • 发布者 (Publisher):事件的发布方,它只管发布特定主题(topic)的事件。
  • 订阅者 (Subscriber):事件的接收方,它订阅自己感兴趣的主题。
  • 事件中心 (Event Hub/Broker):一个中介,负责管理订阅和发布。它维护着一个主题和订阅者列表的映射。

3. 简单实现

我们可以用一个 class 来实现这个模式。在 JavaScript 中实现这个模式需要注意一些事项:

  1. 注意我们使用的是回调函数。
  2. 注意微任务队列的使用:它既保证了不阻塞同步任务、又保证了在根据同步结果进行渲染前先执行异步任务。但是**并不是任何情况都要使用微任务队列的!**之后在分析 TicTacToe 项目实现时会仔细分析:
class PubSub {
  constructor() {
    // 存储所有主题和对应的订阅者回调
    // 格式: { 'topic1': [cb1, cb2], 'topic2': [cb3] }
    this.events = {};
  }

  /**
   * 订阅一个主题
   * @param {string} topic - 主题名称
   * @param {function} callback - 回调函数
   * @returns {object} - 包含 unsubscribe 方法的对象,方便取消订阅
   */
  subscribe(topic, callback) {
    if (!this.events[topic]) {
      this.events[topic] = [];
    }
    this.events[topic].push(callback);

    // 返回一个对象,包含取消订阅的方法
    return {
      unsubscribe: () => this.unsubscribe(topic, callback)
    };
  }

  /**
   * 取消订阅
   * @param {string} topic - 主题名称
   * @param {function} callback - 要移除的回调函数
   */
  unsubscribe(topic, callback) {
    if (this.events[topic]) {
      this.events[topic] = this.events[topic].filter(cb => cb !== callback);
    }
  }

  /**
   * 发布一个主题
   * @param {string} topic - 主题名称
   * @param {*} data - 传递给回调函数的数据
   */
  publish(topic, data) {
    if (this.events[topic]) {
      this.events[topic].forEach(callback => {
        // 使用 queueMicrotask 可以在当前同步代码执行完后,再通知订阅者
        // 这样可以避免在发布过程中,某个订阅者的耗时操作阻塞后续通知
        queueMicrotask(() => {
            callback(data);
        });
      });
    }
  }
}