发布订阅模式通过事件通信解耦模块,eventtarget可原生实现,手写eventbus兼容旧环境,适用于新增/删除监听者而不修改发布者代码,但需注意事件泛滥、内存泄漏和异步顺序问题。

发布订阅模式(Pub/Sub)在 JavaScript 中是解耦流程控制的常用手段——它让模块之间不直接调用,而是通过“事件”通信,从而降低依赖、提升可维护性。核心在于:触发方(发布者)只管发消息,响应方(订阅者)只管监听并处理,双方互不知晓对方存在。
用 EventTarget 实现轻量级 Pub/Sub
现代浏览器和 Node.js(v14.5+)原生支持 EventTarget,无需第三方库,适合大多数流程解耦场景:
- 创建一个共享的事件中心实例:
const eventBus = new EventTarget() - 订阅流程节点:
eventBus.addEventListener('userLogin', handleAuthSuccess) - 在合适时机发布事件:
eventBus.dispatchEvent(new CustomEvent('userLogin', { detail: { userId: 123 } }))
注意:事件名建议用语义化字符串(如 'orderCreated'、'paymentConfirmed'),避免用通用名(如 'done')导致冲突。
手动实现简易 Pub/Sub 类(兼容旧环境)
若需支持 IE 或更细粒度控制(如通配符、一次订阅、取消所有),可手写一个精简类:
class EventBus {
constructor() {
this.events = {}
}
on(type, callback) {
if (!this.events[type]) this.events[type] = []
this.events[type].push(callback)
}
emit(type, data) {
const callbacks = this.events[type] || []
callbacks.forEach(cb => cb(data))
}
off(type, callback) {
if (!this.events[type]) return
this.events[type] = this.events[type].filter(cb => cb !== callback)
}
}
使用示例:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
const bus = new EventBus()<br>
bus.on('apiError', (err) => console.log('统一错误处理:', err))<br>
fetch('/api').catch(e => bus.emit('apiError', e))
用发布订阅替代硬编码流程调用
典型痛点:A 模块完成某操作后,必须显式调用 B、C、D 的方法,导致修改 B 就要改 A —— 这就是紧耦合。换成 Pub/Sub 后:
- A 完成后只发一个事件:
bus.emit('profileUpdated', profile) - B 负责刷新头像:
bus.on('profileUpdated', updateAvatar) - C 负责同步到 WebSocket:
bus.on('profileUpdated', sendToWs) - D 负责埋点上报:
bus.on('profileUpdated', trackProfileEdit)
新增 E 模块?只需加一行 on,完全不用碰 A 的代码。删除 C?删掉对应 on 即可,A 无感知。
注意事项与边界
发布订阅不是万能胶,用错反而增加复杂度:
- 避免事件泛滥:高频操作(如
mousemove)不宜直接 emit 自定义事件,应节流或改用回调 - 注意内存泄漏:组件销毁时记得
off(或用addEventListener的{ once: true }) - 异步顺序不保证:多个
on监听同一事件,执行顺序按注册先后,但不适用于强时序依赖逻辑(此时更适合 Promise 链或 async/await) - 调试难度略升:事件流不如函数调用栈直观,建议配合命名规范 + 简单日志(如
console.debug('[EVENT] userLogin', data))
不复杂但容易忽略:关键在“谁该发、谁该收、事件名是否唯一且可读”,想清楚这三点,Pub/Sub 就真正服务于解耦,而不是制造新混乱。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










