事件总线 EventEmitter 实现
描述
事件总线(EventEmitter)是发布-订阅模式的实现,用于组件间通信。
核心原理
- 发布-订阅模式:发布者不直接调用订阅者,通过事件中心中转
- 事件命名空间:支持按事件名订阅
- once:一次性事件订阅
实现代码
js
class EventEmitter {
constructor() {
this.events = {};
this.maxListeners = 10;
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
if (this.events[event].length > this.maxListeners) {
console.warn(`Possible EventEmitter memory leak, ${this.events[event].length} listeners added.`);
}
return this;
}
once(event, listener) {
const wrapper = (...args) => {
listener.apply(this, args);
this.off(event, wrapper);
};
return this.on(event, wrapper);
}
off(event, listenerToRemove) {
if (!this.events[event]) {
return this;
}
this.events[event] = this.events[event].filter((listener) => listener !== listenerToRemove);
return this;
}
emit(event, ...args) {
if (!this.events[event]) {
return false;
}
this.events[event].forEach((listener) => {
listener.apply(this, args);
});
return true;
}
removeAllListeners(event) {
if (event) {
delete this.events[event];
} else {
this.events = {};
}
return this;
}
listenerCount(event) {
return this.events[event] ? this.events[event].length : 0;
}
listeners(event) {
return this.events[event] ? [...this.events[event]] : [];
}
}使用示例
js
const emitter = new EventEmitter();
// 普通订阅
emitter.on("message", (data) => {
console.log("收到消息:", data);
});
emitter.emit("message", "Hello"); // 收到消息: Hello
// 一次性订阅
emitter.once("connect", () => {
console.log("连接成功!");
});
emitter.emit("connect"); // 连接成功!
emitter.emit("connect"); // 不输出
// 取消订阅
function handler(data) {
console.log("handler:", data);
}
emitter.on("event", handler);
emitter.emit("event", "test"); // handler: test
emitter.off("event", handler);
emitter.emit("event", "test"); // 不输出
// 获取监听器列表
emitter.on("event1", () => {});
emitter.on("event1", () => {});
emitter.on("event2", () => {});
console.log(emitter.listenerCount("event1")); // 2
console.log(emitter.listeners("event1")); // [函数, 函数]实际应用
Vue EventBus(Vue 2)
js
// 创建全局事件总线
const EventBus = new EventEmitter();
// 组件 A:发送事件
EventBus.on('user-login', (user) => {
console.log('用户登录:', user.name);
});
// 组件 B:触发事件
EventBus.emit('user-login', { name: '张三' });
// 组件卸载时清理
beforeUnmount() {
EventBus.off('user-login');
}事件委托
js
class DOMEventEmitter extends EventEmitter {
constructor(element) {
super();
this.element = element;
this.element.addEventListener = ((origFn) => {
return (...args) => {
origFn.apply(this.element, args);
};
})(this.element.addEventListener);
}
}Node.js EventEmitter 对比
| 方法 | 说明 | Node.js |
|---|---|---|
| on | 添加监听器 | ✓ |
| once | 添加一次性监听器 | ✓ |
| off | 移除监听器 | ✓(removeListener) |
| emit | 触发事件 | ✓ |
| removeAllListeners | 移除所有监听器 | ✓ |
| listenerCount | 监听器数量 | ✓(listenerCount) |
| listeners | 获取监听器列表 | ✓ |
面试追问点
发布-订阅模式 vs 观察者模式
| 特性 | 发布-订阅 | 观察者 |
|---|---|---|
| 通信方式 | 通过事件中心 | 直接通信 |
| 耦合度 | 松耦合 | 紧密耦合 |
| 复杂度 | 较高(需事件中心) | 较低 |
| 灵活性 | 高 | 低 |
内存泄漏风险
js
// 未移除监听器可能导致内存泄漏
class Component {
ngOnInit() {
eventBus.on("data", this.handleData); // 未保存引用,无法移除
}
}
// 正确做法
class Component {
constructor() {
this.boundHandler = this.handleData.bind(this);
}
ngOnInit() {
eventBus.on("data", this.boundHandler);
}
ngOnDestroy() {
eventBus.off("data", this.boundHandler);
}
}