文章目录
发布订阅模式(Pub/Sub)是 JavaScript 中一种非常重要的设计模式,它广泛应用于事件处理、消息通信等场景。本文将详细介绍发布订阅模式的原理、实现方式以及实际应用案例。
什么是发布订阅模式?
发布订阅模式是一种消息范式,消息的发送者(发布者)不会直接将消息发送给特定的接收者(订阅者),而是通过消息通道(事件中心)进行广播,订阅者可以接收到自己感兴趣的消息。
核心概念:
- 发布者(Publisher):发出事件/消息的对象
- 订阅者(Subscriber):监听特定事件/消息的对象
- 事件中心(Event Channel):管理事件与订阅者之间的关系
基础实现
1. 最简单的发布订阅实现
class EventEmitter {
constructor() {
this.events = {}; // 存储事件及回调
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 发布事件
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => {
callback(...args);
});
}
}
// 取消订阅
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
cb => cb !== callback
);
}
}
// 一次性订阅
once(eventName, callback) {
const onceCallback = (...args) => {
callback(...args);
this.off(eventName, onceCallback);
};
this.on(eventName, onceCallback);
}
}
// 使用示例
const eventBus = new EventEmitter();
// 订阅事件
eventBus.on('message', (msg) => {
console.log('收到消息:', msg);
});
// 发布事件
eventBus.emit('message', 'Hello World!'); // 输出: 收到消息: Hello World!
// 一次性订阅
eventBus.once('onceEvent', (msg) => {
console.log('一次性事件:', msg);
});
eventBus.emit('onceEvent', '第一次触发'); // 输出: 一次性事件: 第一次触发
eventBus.emit('onceEvent', '第二次触发'); // 无输出
// 取消订阅
const callback = (msg) => {
console.log('将被取消的订阅:', msg);
};
eventBus.on('cancelEvent', callback);
eventBus.emit('cancelEvent', '测试取消前'); // 输出: 将被取消的订阅: 测试取消前
eventBus.off('cancelEvent', callback);
eventBus.emit('cancelEvent', '测试取消后'); // 无输出
高级功能实现
2. 支持命名空间的发布订阅
class AdvancedEventEmitter {
constructor() {
this.events = {};
this.maxListeners = 10; // 默认最大监听器数量
}
// 添加监听器
on(eventName, listener) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
// 检查监听器数量限制
if (this.events[eventName].length >= this.maxListeners) {
console.warn(`Possible memory leak detected. ${this.events[eventName].length} ${eventName} listeners added.`);
}
this.events[eventName].push(listener);
return this; // 支持链式调用
}
// 触发事件
emit(eventName, ...args) {
if (this.events[eventName]) {
const listeners = this.events[eventName].slice(); // 创建副本,避免回调中取消订阅导致的问题
listeners.forEach(listener => {
try {
listener.apply(this, args);
} catch (err) {
console.error(`Error in event listener for ${eventName}:`, err);
}
});
}
return this;
}
// 取消订阅
off(eventName, listener) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
l => l !== listener
);
}
return this;
}
// 一次性订阅
once(eventName, listener) {
const onceWrapper = (...args) => {
listener.apply(this, args);
this.off(eventName, onceWrapper);
};
this.on(eventName, onceWrapper);
return this;
}
// 设置最大监听器数量
setMaxListeners(n) {
this.maxListeners = n;
return this;
}
// 移除所有监听器或指定事件的所有监听器
removeAllListeners(eventName) {
if (eventName) {
delete this.events[eventName];
} else {
this.events = {};
}
return this;
}
// 获取指定事件的监听器数量
listenerCount(eventName) {
return this.events[eventName] ? this.events[eventName].length : 0;
}
}
// 使用示例
const advancedBus = new AdvancedEventEmitter();
// 设置最大监听器数量
advancedBus.setMaxListeners(2);
// 添加多个监听器
advancedBus.on('data', (d) => console.log('Data 1:', d));
advancedBus.on('data', (d) => console.log('Data 2:', d));
advancedBus.on('data', (d) => console.log('Data 3:', d)); // 会收到警告
// 触发事件
advancedBus.emit('data', { id: 1, value: 'test' });
/*
输出:
Data 1: {id: 1, value: "test"}
Data 2: {id: 1, value: "test"}
Data 3: {id: 1, value: "test"}
*/
// 获取监听器数量
console.log(advancedBus.listenerCount('data')); // 3
// 移除所有监听器
advancedBus.removeAllListeners('data');
console.log(advancedBus.listenerCount('data')); // 0
3. 支持异步事件处理的发布订阅
class AsyncEventEmitter {
constructor() {
this.events = {};
this.asyncEvents = {};
}
// 同步订阅
on(eventName, listener) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(listener);
return this;
}
// 异步订阅
onAsync(eventName, listener) {
if (!this.asyncEvents[eventName]) {
this.asyncEvents[eventName] = [];
}
this.asyncEvents[eventName].push(listener);
return this;
}
// 触发事件(同步)
emitSync(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach(listener => {
listener(...args);
});
}
return this;
}
// 触发事件(异步)
async emit(eventName, ...args) {
// 先执行同步监听器
if (this.events[eventName]) {
this.events[eventName].forEach(listener => {
listener(...args);
});
}
// 然后执行异步监听器
if (this.asyncEvents[eventName]) {
for (const listener of this.asyncEvents[eventName]) {
await listener(...args);
}
}
return this;
}
// 取消订阅
off(eventName, listener) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
l => l !== listener
);
}
if (this.asyncEvents[eventName]) {
this.asyncEvents[eventName] = this.asyncEvents[eventName].filter(
l => l !== listener
);
}
return this;
}
}
// 使用示例
const asyncBus = new AsyncEventEmitter();
// 同步订阅
asyncBus.on('syncEvent', (msg) => {
console.log('同步处理:', msg);
});
// 异步订阅
asyncBus.onAsync('asyncEvent', async (msg) => {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('异步处理1:', msg);
});
asyncBus.onAsync('asyncEvent', async (msg) => {
await new Promise(resolve => setTimeout(resolve, 500));
console.log('异步处理2:', msg);
});
// 触发同步事件
console.log('开始同步事件');
asyncBus.emitSync('syncEvent', '同步消息');
console.log('同步事件结束');
// 触发异步事件
console.log('开始异步事件');
asyncBus.emit('asyncEvent', '异步消息').then(() => {
console.log('所有异步处理完成');
});
/*
输出顺序:
开始同步事件
同步处理: 同步消息
同步事件结束
开始异步事件
(等待500ms)
异步处理2: 异步消息
(等待500ms,总共1s)
异步处理1: 异步消息
所有异步处理完成
*/
实际应用场景
1. Vue 中的事件总线
// eventBus.js
class EventBus {
constructor() {
this.events = {};
}
$on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
$emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => {
callback(...args);
});
}
}
$off(event, callback) {
if (!this.events[event]) return;
if (!callback) {
delete this.events[event];
} else {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
// 创建全局事件总线
const bus = new EventBus();
export default bus;
// 组件A.vue - 发布事件
import bus from './eventBus';
bus.$emit('user-logged-in', { userId: 123 });
// 组件B.vue - 订阅事件
import bus from './eventBus';
bus.$on('user-logged-in', (user) => {
console.log('用户登录:', user);
});
2. React 中的跨组件通信
// EventService.js
class EventService {
constructor() {
this.listeners = {};
}
subscribe(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
const listenerId = Symbol();
this.listeners[event].push({ id: listenerId, callback });
return listenerId;
}
publish(event, data) {
if (!this.listeners[event]) return;
this.listeners[event].forEach(listener => {
listener.callback(data);
});
}
unsubscribe(event, listenerId) {
if (!this.listeners[event]) return;
this.listeners[event] = this.listeners[event].filter(
listener => listener.id !== listenerId
);
}
}
const eventService = new EventService();
export default eventService;
// ComponentA.jsx - 发布事件
import React from 'react';
import eventService from './EventService';
const ComponentA = () => {
const handleClick = () => {
eventService.publish('button-clicked', { time: new Date() });
};
return <button onClick={handleClick}>点击我</button>;
};
// ComponentB.jsx - 订阅事件
import React, { useEffect, useState } from 'react';
import eventService from './EventService';
const ComponentB = () => {
const [lastClick, setLastClick] = useState(null);
useEffect(() => {
const listenerId = eventService.subscribe('button-clicked', data => {
setLastClick(data.time);
});
return () => {
eventService.unsubscribe('button-clicked', listenerId);
};
}, []);
return (
<div>
最后一次点击时间: {lastClick ? lastClick.toString() : '暂无'}
</div>
);
};
性能优化与注意事项
-
内存管理:
- 及时取消不再需要的事件监听
- 避免在短时间内大量添加监听器
- 设置合理的最大监听器数量
-
错误处理:
- 监听器内部应该做好错误捕获
- 可以考虑添加全局错误处理回调
-
性能优化:
- 对于高频事件,可以考虑使用防抖或节流
- 避免在监听器中执行耗时操作
-
扩展功能:
- 添加事件过滤器
- 支持事件优先级
- 添加中间件机制
完整实现代码
下面是一个功能完整的发布订阅实现,包含了上述所有优化点:
class UltimateEventEmitter {
constructor(options = {}) {
this._events = {};
this._maxListeners = options.maxListeners || 10;
this._middlewares = options.middlewares || [];
this._errorHandler = options.errorHandler || console.error;
}
// 添加中间件
use(middleware) {
this._middlewares.push(middleware);
return this;
}
// 应用中间件
_applyMiddlewares(eventName, data) {
return this._middlewares.reduce((result, middleware) => {
try {
return middleware(eventName, result) || result;
} catch (err) {
this._errorHandler(`Middleware error for event "${eventName}":`, err);
return result;
}
}, data);
}
// 添加事件监听
on(eventName, listener, options = {}) {
if (typeof listener !== 'function') {
throw new TypeError('Listener must be a function');
}
if (!this._events[eventName]) {
this._events[eventName] = [];
}
// 检查监听器数量限制
if (this._events[eventName].length >= this._maxListeners) {
console.warn(`Possible memory leak detected. ${this._events[eventName].length} ${eventName} listeners added.`);
}
const listenerObj = {
listener,
once: !!options.once,
priority: options.priority || 0,
context: options.context || this
};
// 按优先级插入
const listeners = this._events[eventName];
let i = listeners.length;
while (i > 0 && listeners[i - 1].priority > listenerObj.priority) {
i--;
}
listeners.splice(i, 0, listenerObj);
return this;
}
// 一次性监听
once(eventName, listener, options = {}) {
return this.on(eventName, listener, { ...options, once: true });
}
// 触发事件
emit(eventName, ...args) {
if (!this._events[eventName]) return false;
// 应用中间件
let processedArgs = args;
try {
processedArgs = this._applyMiddlewares(eventName, args);
} catch (err) {
this._errorHandler(`Middleware processing error for event "${eventName}":`, err);
return false;
}
// 创建监听器副本并排序
const listeners = this._events[eventName].slice().sort((a, b) => b.priority - a.priority);
let result;
for (let i = 0; i < listeners.length; i++) {
const { listener, once, context } = listeners[i];
try {
const listenerResult = listener.apply(context, processedArgs);
// 如果某个监听器返回false,则停止后续监听器执行
if (listenerResult === false) {
result = false;
break;
}
result = result || listenerResult;
} catch (err) {
this._errorHandler(`Error in listener for event "${eventName}":`, err);
}
// 一次性监听器移除
if (once) {
this.off(eventName, listener);
}
}
return result;
}
// 异步触发事件
async emitAsync(eventName, ...args) {
if (!this._events[eventName]) return;
// 应用中间件
let processedArgs = args;
try {
processedArgs = await this._applyMiddlewares(eventName, args);
} catch (err) {
this._errorHandler(`Middleware processing error for event "${eventName}":`, err);
return;
}
// 创建监听器副本并排序
const listeners = this._events[eventName].slice().sort((a, b) => b.priority - a.priority);
for (let i = 0; i < listeners.length; i++) {
const { listener, once, context } = listeners[i];
try {
await listener.apply(context, processedArgs);
} catch (err) {
this._errorHandler(`Error in async listener for event "${eventName}":`, err);
}
// 一次性监听器移除
if (once) {
this.off(eventName, listener);
}
}
}
// 移除事件监听
off(eventName, listener) {
if (!this._events[eventName]) return this;
if (!listener) {
delete this._events[eventName];
return this;
}
this._events[eventName] = this._events[eventName].filter(
l => l.listener !== listener
);
if (this._events[eventName].length === 0) {
delete this._events[eventName];
}
return this;
}
// 移除所有监听器
removeAllListeners(eventName) {
if (eventName) {
delete this._events[eventName];
} else {
this._events = {};
}
return this;
}
// 获取监听器数量
listenerCount(eventName) {
if (!eventName) {
return Object.values(this._events).reduce(
(count, listeners) => count + listeners.length, 0
);
}
return this._events[eventName] ? this._events[eventName].length : 0;
}
// 设置最大监听器数量
setMaxListeners(n) {
if (typeof n !== 'number' || n < 0) {
throw new TypeError('n must be a positive number');
}
this._maxListeners = n;
return this;
}
// 获取所有事件名
eventNames() {
return Object.keys(this._events);
}
}
// 使用示例
const emitter = new UltimateEventEmitter({
maxListeners: 2,
errorHandler: (err) => {
console.error('Custom error handler:', err);
}
});
// 添加中间件
emitter.use((eventName, data) => {
console.log(`Middleware: event "${eventName}" with data`, data);
if (eventName === 'filtered') {
return null; // 过滤掉这个事件
}
return data.map(item => item * 2); // 修改数据
});
// 添加监听器
emitter.on('data', (num) => {
console.log('Data received (normal priority):', num);
});
emitter.on('data', (num) => {
console.log('Data received (high priority):', num);
}, { priority: 1 });
emitter.once('once', (msg) => {
console.log('Once event:', msg);
});
// 触发事件
emitter.emit('data', 1, 2, 3);
/*
Middleware: event "data" with data [1, 2, 3]
Data received (high priority): [2, 4, 6]
Data received (normal priority): [2, 4, 6]
*/
emitter.emit('once', 'first time');
// Once event: first time
emitter.emit('once', 'second time'); // 不会触发
emitter.emit('filtered', 'should not appear'); // 被中间件过滤
总结
发布订阅模式是 JavaScript 中非常重要的设计模式,它能够实现组件间的松耦合通信。本文介绍了从基础到高级的多种实现方式,包括:
- 基础的事件发布订阅实现
- 支持命名空间和错误处理的高级实现
- 支持异步事件处理的实现
- 在实际框架(Vue/React)中的应用
- 完整的生产级实现
在实际开发中,可以根据项目需求选择合适的实现方式。对于简单的场景,基础实现就足够了;对于复杂的应用,建议使用功能更完整的实现或现有的成熟库(如 EventEmitter3、RxJS 等)。
关键点总结:
- 发布订阅模式的核心是维护一个事件-监听器的映射关系
- 需要注意内存管理和错误处理
- 中间件机制可以增强事件处理能力
- 异步事件处理在现代应用中越来越重要
- 合理的优先级系统可以解决监听器执行顺序问题