什么是发布订阅模式?JavaScript 实现一个发布订阅模式的示例
·
什么是发布订阅模式?
发布订阅模式(Publish-Subscribe Pattern)是一种行为型设计模式,它通过引入一个 “事件中心”(或 “消息代理”),实现发布者(发布事件的对象)与订阅者(接收事件的对象)的解耦。
其核心思想是:
- 订阅者通过事件中心订阅感兴趣的事件,并注册回调函数。
- 发布者通过事件中心发布事件,事件中心会自动通知所有订阅该事件的订阅者,执行其回调函数。
- 发布者和订阅者之间无需直接关联,完全通过事件中心通信,降低了对象间的耦合度。
发布订阅模式的核心角色
- 事件中心(Event Bus):维护事件与订阅者的映射关系,提供订阅(
on)、发布(emit)、取消订阅(off)等接口。 - 发布者(Publisher):通过事件中心发布事件,无需知道谁在订阅。
- 订阅者(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", "第四次消息"); // 无输出(已无订阅者)
代码说明
-
事件中心(EventBus):
events属性:用对象存储事件映射,键为事件名,值为回调函数数组(每个元素包含callback和once标记)。on方法:注册订阅,支持普通订阅和一次性订阅(通过once参数控制)。once方法:简化一次性订阅(内部调用on并传入once: true)。emit方法:触发事件,遍历并执行该事件的所有回调,传递参数;若为一次性订阅,执行后自动移除。off方法:取消订阅,可取消指定回调或该事件的所有订阅。
-
发布者与订阅者:
- 订阅者通过
eventBus.on()注册回调,无需知道发布者是谁。 - 发布者通过
eventBus.emit()触发事件,无需知道谁在订阅。 - 两者完全解耦,例如 “登录事件” 的发布者(登录按钮)和订阅者(用户信息面板、权限管理器)可独立修改。
- 订阅者通过
发布订阅模式的优势
- 解耦性:发布者和订阅者互不依赖,一方的修改不会影响另一方,降低系统耦合度。
- 灵活性:可动态添加 / 移除订阅,支持多对多通信(一个事件可被多个订阅者接收,一个订阅者可订阅多个事件)。
- 可扩展性:新增功能时,只需添加新的订阅者或发布者,无需修改现有代码,符合 “开闭原则”。
常见应用场景
- 前端事件系统:如浏览器的
addEventListener(DOM 事件本质是发布订阅模式)。 - 状态管理:如 Vuex、Redux 中的状态变更通知(组件订阅状态变化,状态更新时自动触发重新渲染)。
- 跨组件通信:如 React 中无直接关联的组件通过事件总线通信。
- 异步编程:如 Promise、回调函数的扩展(多个回调等待同一个异步操作完成)。
与观察者模式的区别
发布订阅模式是观察者模式的升级版:
- 观察者模式中,观察者直接依赖被观察者(观察者需注册到被观察者中)。
- 发布订阅模式通过事件中心间接通信,发布者和订阅者完全解耦,更适合复杂系统。
发布订阅模式的核心是 “事件中介,解耦通信”,它通过事件中心实现了对象间的灵活交互,是前端开发中最常用的设计模式之一(如事件总线、状态管理等场景广泛应用)。
更多推荐


所有评论(0)