🎯 一、为什么要掌握发布订阅?
发布订阅是前端的“根设计模式”:
- Vue2 的事件总线
$on / $emit就是它 - React 中的 Hook 管理器、Redux 中间件也用到
- 微前端、组件通信、埋点系统、日志系统、消息队列……全靠它!
🧠 二、设计模式简述
✅ 发布订阅(Pub-Sub):
- 发布者不直接通知订阅者,而是将消息交给调度中心
- 订阅者注册回调函数,等待被通知
与之对比的观察者模式中,观察者被直接挂到被观察对象上,耦合更强。
✍️ 三、最简版 EventEmitter 实现(核心功能)
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 取消订阅
off(eventName, callback) {
if (!this.events[eventName]) return;
this.events[eventName] = this.events[eventName].filter(fn => fn !== callback);
}
// 发布
emit(eventName, ...args) {
if (!this.events[eventName]) return;
this.events[eventName].forEach(fn => fn(...args));
}
}
✅ 四、使用示例
const bus = new EventEmitter();
function handler(data) {
console.log('接收到事件:', data);
}
bus.on('login', handler);
bus.emit('login', { user: 'Mark' }); // 接收到事件:{ user: 'Mark' }
bus.off('login', handler);
bus.emit('login', { user: 'Mark' }); // 无输出
🔁 五、支持一次订阅 once(高级功能)
once(eventName, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
🎯 六、完整版本(带 once + 防止重复)
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
(this.events[event] ||= []).push(callback);
}
off(event, callback) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(fn => fn !== callback);
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
emit(event, ...args) {
if (!this.events[event]) return;
[...this.events[event]].forEach(fn => fn(...args)); // 防止事件中删除自己
}
}
🧩 七、场景举例(项目应用)
| 场景 | 用法 |
|---|---|
| 微前端通信 | bus.emit('app:loaded') |
| 表单联动 | A 表单 on('fieldXChange'),B 表单监听 |
| 日志/埋点 | emit 事件上报 |
| 前端异常上报 | on('error') → sentry.report(...) |
❗ 八、面试常见陷阱
| 问题 | 正确处理 |
|---|---|
| emit 时删订阅项会出错? | 使用浅拷贝 [].forEach() |
| 同事件多次 on,怎么防重? | 使用 Set 或手动去重 |
| 如何实现全局唯一事件总线? | 封装 export const bus = new EventEmitter() |