可用ES6 Class封装轻量Pub/Sub类,核心是用Map存储事件名与回调数组映射,支持on/emit/off/once,自动初始化事件队列、安全遍历、静默忽略未注册事件。

可以用 ES6 Class 封装一个轻量、易用的发布订阅(Pub/Sub)类,核心是维护事件名与回调函数列表的映射关系,支持注册(on)、触发(emit)和取消(off)。
定义 PubSub 类结构
创建一个 PubSub 类,内部用 Map 或普通对象存储事件监听器。推荐用 Map,避免原型污染且支持任意类型作为事件名(如 Symbol)。
- 构造函数初始化一个 handlers Map,键为事件名,值为回调函数数组
- on(event, fn):将回调推入对应事件的队列;支持重复注册相同函数
- emit(event, ...args):遍历该事件所有回调并执行,传入参数
- off(event, fn?):不传 fn 则清空整个事件;传了 fn 则只移除指定回调
支持一次性监听(once)
在 on 的基础上扩展 once(event, fn) 方法:注册后,在首次触发时自动调用并立即卸载。
- 内部可封装一个带标记的包装函数,执行一次后从 handlers 中删除自身
- 注意:需确保包装函数能被 off 正确识别并移除(例如用 WeakMap 关联原始 fn 和包装 fn)
- 更简单做法:直接在 emit 中检测并清理,但需额外标记逻辑
处理事件名不存在或无监听器的情况
emit 时若 event 不在 handlers 中,应静默忽略,不报错;on 时若事件首次注册,需先初始化对应数组。
- 可在 on 中用 handlers.set(event, handlers.get(event) || []) 确保数组存在
- emit 中用 handlers.get(event)?.forEach(...) 安全遍历,避免 undefined 报错
- off 清空后可选择 delete handlers.delete(event),节省内存(非必须)
使用示例与注意事项
实例化后即可按需订阅、发布:
const bus = new PubSub();bus.on('user:login', (user) => console.log('欢迎', user.name));
bus.emit('user:login', { name: 'Alice' }); // 输出:欢迎 Alice
- 避免在回调中长时间阻塞主线程,否则影响其他监听器执行
- 注意 this 指向:fn 默认以 bus 为 this 调用,如需绑定特定上下文,建议由使用者自行 bind 或用箭头函数
- 如需跨模块通信,可导出单例实例,或让各模块注入同一 bus 实例


















