什么是发布订阅模式?

发布订阅模式(Publish-Subscribe Pattern)是一种行为型设计模式,它通过引入一个 “事件中心”(或 “消息代理”),实现发布者(发布事件的对象)与订阅者(接收事件的对象)的解耦。

其核心思想是:

  • 订阅者通过事件中心订阅感兴趣的事件,并注册回调函数。
  • 发布者通过事件中心发布事件,事件中心会自动通知所有订阅该事件的订阅者,执行其回调函数。
  • 发布者和订阅者之间无需直接关联,完全通过事件中心通信,降低了对象间的耦合度。

发布订阅模式的核心角色

  1. 事件中心(Event Bus):维护事件与订阅者的映射关系,提供订阅(on)、发布(emit)、取消订阅(off)等接口。
  2. 发布者(Publisher):通过事件中心发布事件,无需知道谁在订阅。
  3. 订阅者(Subscriber):通过事件中心订阅事件,注册回调函数,无需知道谁在发布。

JavaScript 实现发布订阅模式示例:事件总线

以下实现一个通用的事件总线(EventBus),支持多事件订阅、触发、取消订阅,以及一次性订阅(触发后自动取消)。

代码实现
class EventBus {
  constructor() {
    // 存储事件与订阅者的映射:{ 事件名: [回调函数1, 回调函数2, ...] }
    this.events = {};
  }

  /**
   * 订阅事件
   * @param {string} eventName - 事件名称
   * @param {Function} callback - 回调函数
   * @param {boolean} [once=false] - 是否为一次性订阅(触发后自动取消)
   */
  on(eventName, callback, once = false) {
    if (typeof callback !== "function") {
      throw new Error("回调函数必须是函数类型");
    }
    // 初始化事件对应的回调数组
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    // 存储回调及是否为一次性订阅的标记
    this.events[eventName].push({ callback, once });
  }

  /**
   * 一次性订阅(触发后自动取消)
   * @param {string} eventName - 事件名称
   * @param {Function} callback - 回调函数
   */
  once(eventName, callback) {
    this.on(eventName, callback, true);
  }

  /**
   * 发布事件(触发订阅者的回调)
   * @param {string} eventName - 事件名称
   * @param  {...any} args - 传递给回调的参数
   */
  emit(eventName, ...args) {
    const callbacks = this.events[eventName];
    if (!callbacks || callbacks.length === 0) {
      return; // 无订阅者,直接返回
    }
    // 遍历回调并执行(注意:需复制数组,避免执行中修改原数组导致问题)
    const callbacksCopy = [...callbacks];
    callbacksCopy.forEach(({ callback, once }, index) => {
      callback(...args); // 执行回调,传递参数
      // 若为一次性订阅,从原数组中移除
      if (once) {
        this.events[eventName].splice(index, 1);
      }
    });
  }

  /**
   * 取消订阅
   * @param {string} eventName - 事件名称
   * @param {Function} [callback] - 要取消的回调(不传则取消该事件的所有订阅)
   */
  off(eventName, callback) {
    const callbacks = this.events[eventName];
    if (!callbacks || callbacks.length === 0) {
      return;
    }
    // 若不传 callback,清空该事件的所有订阅
    if (!callback) {
      this.events[eventName] = [];
      return;
    }
    // 只移除指定的 callback
    this.events[eventName] = callbacks.filter(item => item.callback !== callback);
  }
}

// -------------- 客户端使用示例 --------------
// 创建事件总线
const eventBus = new EventBus();

// 示例1:基本订阅与发布
function handleMessage(data) {
  console.log("收到消息:", data);
}
// 订阅 "message" 事件
eventBus.on("message", handleMessage);
// 发布 "message" 事件
eventBus.emit("message", "Hello, 发布订阅模式!"); // 输出:收到消息: Hello, 发布订阅模式!

// 示例2:多订阅者
eventBus.on("message", (data) => {
  console.log("另一个订阅者收到消息:", data);
});
eventBus.emit("message", "第二次消息");
// 输出:
// 收到消息: 第二次消息
// 另一个订阅者收到消息: 第二次消息

// 示例3:一次性订阅(once)
eventBus.once("login", (user) => {
  console.log("用户登录(仅触发一次):", user);
});
eventBus.emit("login", "Alice"); // 输出:用户登录(仅触发一次): Alice
eventBus.emit("login", "Bob"); // 无输出(已自动取消订阅)

// 示例4:取消订阅(off)
eventBus.off("message", handleMessage); // 取消 handleMessage 对 "message" 的订阅
eventBus.emit("message", "第三次消息"); // 输出:另一个订阅者收到消息: 第三次消息(仅剩余订阅者触发)

eventBus.off("message"); // 取消 "message" 的所有订阅
eventBus.emit("message", "第四次消息"); // 无输出(已无订阅者)

代码说明

  1. 事件中心(EventBus)

    • events 属性:用对象存储事件映射,键为事件名,值为回调函数数组(每个元素包含 callback 和 once 标记)。
    • on 方法:注册订阅,支持普通订阅和一次性订阅(通过 once 参数控制)。
    • once 方法:简化一次性订阅(内部调用 on 并传入 once: true)。
    • emit 方法:触发事件,遍历并执行该事件的所有回调,传递参数;若为一次性订阅,执行后自动移除。
    • off 方法:取消订阅,可取消指定回调或该事件的所有订阅。
  2. 发布者与订阅者

    • 订阅者通过 eventBus.on() 注册回调,无需知道发布者是谁。
    • 发布者通过 eventBus.emit() 触发事件,无需知道谁在订阅。
    • 两者完全解耦,例如 “登录事件” 的发布者(登录按钮)和订阅者(用户信息面板、权限管理器)可独立修改。

发布订阅模式的优势

  • 解耦性:发布者和订阅者互不依赖,一方的修改不会影响另一方,降低系统耦合度。
  • 灵活性:可动态添加 / 移除订阅,支持多对多通信(一个事件可被多个订阅者接收,一个订阅者可订阅多个事件)。
  • 可扩展性:新增功能时,只需添加新的订阅者或发布者,无需修改现有代码,符合 “开闭原则”。

常见应用场景

  • 前端事件系统:如浏览器的 addEventListener(DOM 事件本质是发布订阅模式)。
  • 状态管理:如 Vuex、Redux 中的状态变更通知(组件订阅状态变化,状态更新时自动触发重新渲染)。
  • 跨组件通信:如 React 中无直接关联的组件通过事件总线通信。
  • 异步编程:如 Promise、回调函数的扩展(多个回调等待同一个异步操作完成)。

与观察者模式的区别

发布订阅模式是观察者模式的升级版

  • 观察者模式中,观察者直接依赖被观察者(观察者需注册到被观察者中)。
  • 发布订阅模式通过事件中心间接通信,发布者和订阅者完全解耦,更适合复杂系统。

发布订阅模式的核心是 “事件中介,解耦通信”,它通过事件中心实现了对象间的灵活交互,是前端开发中最常用的设计模式之一(如事件总线、状态管理等场景广泛应用)。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐