引言
在 JavaScript 开发中,事件驱动编程是构建可维护、可扩展应用的核心范式之一。从浏览器 DOM 事件到 Node.js 的异步 I/O,从 Vue 的组件通信到 React 的状态管理,发布订阅模式无处不在。
通过手写一个符合 Node.js EventEmitter 标准的实现,我们不仅能深入理解事件驱动架构的设计原理,还能掌握 JavaScript 中闭包、内存管理、设计模式等核心概念。更重要的是,这是面试中常见的高级题目,能体现你对JavaScript设计模式的理解深度。
本文将带你从零实现一个功能完整的EventEmitter,并探讨其在实际项目中的应用和优化策略。
一、发布订阅模式的核心概念
1.1 什么是发布订阅模式
发布订阅模式(Pub/Sub)是一种消息传递范式,消息的发送者(发布者)不会将消息直接发送给特定的接收者(订阅者),而是通过一个中间件(事件中心)进行通信。
1// 类比:报纸订阅系统 2class NewspaperSystem { 3 constructor() { 4 this.subscribers = new Map(); // 事件中心 5 } 6 7 // 订阅(读者订阅报纸) 8 subscribe(topic, reader) { 9 if (!this.subscribers.has(topic)) { 10 this.subscribers.set(topic, []); 11 } 12 this.subscribers.get(topic).push(reader); 13 } 14 15 // 发布(报社发布新闻) 16 publish(topic, news) { 17 if (this.subscribers.has(topic)) { 18 this.subscribers.get(topic).forEach(reader => reader(news)); 19 } 20 } 21 22 // 取消订阅(读者退订) 23 unsubscribe(topic, reader) { 24 if (this.subscribers.has(topic)) { 25 const readers = this.subscribers.get(topic); 26 const index = readers.indexOf(reader); 27 if (index > -1) { 28 readers.splice(index, 1); 29 } 30 } 31 } 32} 33
1.2 核心组件
- 事件中心(EventEmitter): 存储事件和回调的对应关系
- 发布者(Publisher): 触发事件,传递数据
- 订阅者(Subscriber): 监听事件,处理数据
1.3 与观察者模式的对比
| 特性 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 耦合度 | 直接耦合 | 通过事件中心解耦 |
| 通信方式 | 直接调用 | 间接通信 |
| 灵活性 | 较低 | 更高 |
| 典型实现 | Vue响应式 | NodeJS EventEmitter |
二、基础 EventEmitter 实现
2.1 最小可行实现
1class EventEmitter { 2 constructor() { 3 // 存储事件和对应的回调函数 4 this.events = new Map(); 5 } 6 7 /** 8 * 订阅事件 9 * @param {string} eventName 事件名称 10 * @param {Function} callback 回调函数 11 * @returns {Function} 取消订阅的函数 12 */ 13 on(eventName, callback) { 14 if (!this.events.has(eventName)) { 15 this.events.set(eventName, []); 16 } 17 this.events.get(eventName).push(callback); 18 19 // 返回取消订阅的函数 20 return () => this.off(eventName, callback); 21 } 22 23 /** 24 * 触发事件 25 * @param {string} eventName 事件名称 26 * @param {...any} args 传递给回调函数的参数 27 */ 28 emit(eventName, ...args) { 29 if (!this.events.has(eventName)) { 30 return; 31 } 32 33 const callbacks = this.events.get(eventName); 34 callbacks.forEach((callback) => { 35 try { 36 callback.apply(null, args); 37 } catch (error) { 38 console.error(`Error in event listener for ${eventName}:`, error); 39 } 40 }); 41 } 42 43 /** 44 * 取消订阅 45 * @param {string} eventName 事件名称 46 * @param {Function} callback 要移除的回调函数 47 */ 48 off(eventName, callback) { 49 if (!this.events.has(eventName)) { 50 return; 51 } 52 53 const callbacks = this.events.get(eventName); 54 const index = callbacks.indexOf(callback); 55 56 if (index > -1) { 57 callbacks.splice(index, 1); 58 59 // 如果没有回调函数了, 删除这个事件 60 if (callbacks.length === 0) { 61 this.events.delete(eventName); 62 } 63 } 64 } 65 66 /** 67 * 一次性订阅 68 * @param {string} eventName 事件名称 69 * @param {Function} callback 回调函数 70 */ 71 once(eventName, callback) { 72 const onceCallback = (...args) => { 73 callback.apply(null, args); 74 this.off(eventName, callback); 75 }; 76 77 this.on(eventName, onceCallback); 78 return () => this.off(eventName, onceCallback); 79 } 80 81 /** 82 * 移除所有事件监听器, 或指定事件的所有监听器 83 * @param {string} eventName 可选, 事件名称 84 */ 85 removeAllListeners(eventName) { 86 if (eventName) { 87 this.events.delete(eventName); 88 } else { 89 this.events.clear(); 90 } 91 } 92} 93 94// 基础使用示例 95const emitter = new EventEmitter(); 96 97// 订阅事件 98const unsubscribe = emitter.on("message", (data) => { 99 console.log("收到消息:", data); 100}); 101 102// 触发事件 103emitter.emit("message", "Hello World!"); // 输出: 收到消息: Hello World! 104 105// 取消订阅 106unsubscribe(); 107 108// 再次触发,不会有输出 109emitter.emit("message", "Hello Again!"); 110
三、完整的 EventEmitter 实现
3.1 支持更多特性的完整实现
1class EventEmitter { 2 constructor() { 3 // 使用 Object.create(null) 避免原型污染 4 this._events = Object.create(null); 5 this._maxListeners = 10; 6 } 7 8 // ================ 核心方法 ================ 9 10 /** 11 * 添加事件监听器 12 * @param {string} eventName 事件名称 13 * @param {Function} listener 监听器函数 14 * @returns {EventEmitter} this 15 */ 16 on(eventName, listener) { 17 return this._addListener(eventName, listener, false); 18 } 19 20 /** 21 * 添加事件监听器(别名) 22 */ 23 addListener(eventName, listener) { 24 return this.on(eventName, listener); 25 } 26 27 /** 28 * 添加一次性事件监听器 29 * @param {string} eventName 事件名称 30 * @param {Function} listener 监听器函数 31 * @returns {EventEmitter} this 32 */ 33 once(eventName, listener) { 34 return this._addListener(eventName, listener, true); 35 } 36 37 /** 38 * 触发事件 39 * @param {string} eventName 事件名称 40 * @param {...any} args 传递给监听器的参数 41 * @returns {boolean} 是否有监听器被调用 42 */ 43 emit(eventName, ...args) { 44 if (!this._events[eventName]) { 45 // 如果没有 error 事件的监听器,抛出错误 46 if (eventName === "error") { 47 const error = args[0]; 48 if (error instanceof Error) { 49 throw error; 50 } else { 51 throw new Error("Unhandled error event"); 52 } 53 } 54 return false; 55 } 56 57 const listeners = this._events[eventName]; 58 const listenersCopy = listeners.slice(); // 创建副本避免迭代时修改 59 60 let called = false; 61 62 for (const listener of listenersCopy) { 63 try { 64 // 检查是否为 once 包装函数 65 if (listener._once) { 66 // 移除原始监听器 67 this._removeListener(eventName, listener); 68 } 69 70 listener.apply(this, args); 71 called = true; 72 } catch (error) { 73 // 触发错误事件 74 if (eventName !== "error") { 75 this.emit("error", error); 76 } 77 } 78 } 79 80 return called; 81 } 82 83 /** 84 * 移除事件监听器 85 * @param {string} eventName 事件名称 86 * @param {Function} listener 要移除的监听器 87 * @returns {EventEmitter} this 88 */ 89 off(eventName, listener) { 90 return this.removeListener(eventName, listener); 91 } 92 93 /** 94 * 移除事件监听器 95 * @param {string} eventName 事件名称 96 * @param {Function} listener 要移除的监听器 97 * @returns {EventEmitter} this 98 */ 99 removeListener(eventName, listener) { 100 return this._removeListener(eventName, listener); 101 } 102 103 /** 104 * 移除所有事件监听器 105 * @param {string} [eventName] 可选,事件名称 106 * @returns {EventEmitter} this 107 */ 108 removeAllListeners(eventName) { 109 if (eventName) { 110 delete this._events[eventName]; 111 } else { 112 this._events = Object.create(null); 113 } 114 return this; 115 } 116 117 // ================ 辅助方法 ================ 118 119 /** 120 * 设置最大监听器数量 121 * @param {number} n 最大监听器数量 122 * @returns {EventEmitter} this 123 */ 124 setMaxListeners(n) { 125 if (typeof n !== "number" || n < 0) { 126 throw new TypeError("n must be a non-negative number"); 127 } 128 this._maxListeners = n; 129 return this; 130 } 131 132 /** 133 * 获取最大监听器数量 134 * @returns {number} 最大监听器数量 135 */ 136 getMaxListeners() { 137 return this._maxListeners; 138 } 139 140 /** 141 * 获取指定事件的监听器数量 142 * @param {string} eventName 事件名称 143 * @returns {number} 监听器数量 144 */ 145 listenerCount(eventName) { 146 if (!this._events[eventName]) { 147 return 0; 148 } 149 return this._events[eventName].length; 150 } 151 152 /** 153 * 获取所有事件名称 154 * @returns {string[]} 事件名称数组 155 */ 156 eventNames() { 157 return Object.keys(this._events); 158 } 159 160 /** 161 * 获取指定事件的所有监听器 162 * @param {string} eventName 事件名称 163 * @returns {Function[]} 监听器数组 164 */ 165 listeners(eventName) { 166 if (!this._events[eventName]) { 167 return []; 168 } 169 // 返回副本,避免外部修改内部数组 170 return this._events[eventName].slice(); 171 } 172 173 /** 174 * 添加监听器到数组开头 175 * @param {string} eventName 事件名称 176 * @param {Function} listener 监听器函数 177 * @returns {EventEmitter} this 178 */ 179 prependListener(eventName, listener) { 180 return this._addListener(eventName, listener, false, true); 181 } 182 183 /** 184 * 添加一次性监听器到数组开头 185 * @param {string} eventName 事件名称 186 * @param {Function} listener 监听器函数 187 * @returns {EventEmitter} this 188 */ 189 prependOnceListener(eventName, listener) { 190 return this._addListener(eventName, listener, true, true); 191 } 192 193 /** 194 * 内部方法:添加监听器 195 * @private 196 */ 197 _addListener(eventName, listener, once = false, prepend = false) { 198 if (typeof listener !== "function") { 199 throw new TypeError("listener must be a function"); 200 } 201 202 // 初始化事件数组 203 if (!this._events[eventName]) { 204 this._events[eventName] = []; 205 } 206 207 const listeners = this._events[eventName]; 208 209 // 检查最大监听器限制 210 if (listeners.length >= this._maxListeners && this._maxListeners !== 0) { 211 console.warn( 212 `MaxListenersExceededWarning: Possible EventEmitter memory leak detected. ` + 213 `${listeners.length} ${eventName} listeners added. ` + 214 `Use emitter.setMaxListeners() to increase limit` 215 ); 216 } 217 218 // 如果是 once,创建包装函数 219 let listenerToAdd = listener; 220 if (once) { 221 const onceWrapper = (...args) => { 222 listener.apply(this, args); 223 // 标记为 once 包装函数 224 onceWrapper._once = true; 225 }; 226 // 保存原始监听器引用,用于移除 227 onceWrapper._originalListener = listener; 228 listenerToAdd = onceWrapper; 229 } 230 231 // 添加到数组开头或结尾 232 if (prepend) { 233 listeners.unshift(listenerToAdd); 234 } else { 235 listeners.push(listenerToAdd); 236 } 237 238 return this; 239 } 240 241 /** 242 * 内部方法:移除监听器 243 * @private 244 */ 245 _removeListener(eventName, listener) { 246 if (!this._events[eventName]) { 247 return this; 248 } 249 250 const listeners = this._events[eventName]; 251 252 // 查找要移除的监听器 253 // 需要考虑两种情况: 254 // 1. 直接传入监听器 255 // 2. 传入 once 包装函数的原始监听器 256 let index = -1; 257 258 // 尝试直接查找 259 index = listeners.indexOf(listener); 260 261 // 如果没找到,尝试查找原始监听器 262 if (index === -1) { 263 for (let i = 0; i < listeners.length; i++) { 264 const current = listeners[i]; 265 if (current._originalListener === listener) { 266 index = i; 267 break; 268 } 269 } 270 } 271 272 if (index > -1) { 273 listeners.splice(index, 1); 274 275 // 如果数组为空,删除事件 276 if (listeners.length === 0) { 277 delete this._events[eventName]; 278 } 279 } 280 281 return this; 282 } 283} 284
3.2 类型安全的TypeScript版本
1type Listener = (...args: any[]) => void; 2type OnceWrapper = Listener & { _originalListener?: Listener; _once?: boolean }; 3 4class EventEmitter { 5 private _events: Record<string, Listener[]> = Object.create(null); 6 private _maxListeners: number = 10; 7 8 // 核心方法 9 on(eventName: string, listener: Listener): this { 10 return this._addListener(eventName, listener, false); 11 } 12 13 addListener(eventName: string, listener: Listener): this { 14 return this.on(eventName, listener); 15 } 16 17 once(eventName: string, listener: Listener): this { 18 return this._addListener(eventName, listener, true); 19 } 20 21 emit(eventName: string, ...args: any[]): boolean { 22 const listeners = this._events[eventName]; 23 if (!listeners) { 24 if (eventName === 'error') { 25 const error = args[0]; 26 throw error instanceof Error ? error : new Error('Unhandled error event'); 27 } 28 return false; 29 } 30 31 const listenersCopy = listeners.slice(); 32 let called = false; 33 34 for (const listener of listenersCopy) { 35 try { 36 // 检查是否为 once 包装函数 37 const onceWrapper = listener as OnceWrapper; 38 if (onceWrapper._once) { 39 this._removeListener(eventName, listener); 40 } 41 42 listener.apply(this, args); 43 called = true; 44 } catch (error) { 45 if (eventName !== 'error') { 46 this.emit('error', error); 47 } 48 } 49 } 50 51 return called; 52 } 53 54 off(eventName: string, listener: Listener): this { 55 return this.removeListener(eventName, listener); 56 } 57 58 removeListener(eventName: string, listener: Listener): this { 59 return this._removeListener(eventName, listener); 60 } 61 62 removeAllListeners(eventName?: string): this { 63 if (eventName) { 64 delete this._events[eventName]; 65 } else { 66 this._events = Object.create(null); 67 } 68 return this; 69 } 70 71 // 辅助方法 72 setMaxListeners(n: number): this { 73 if (typeof n !== 'number' || n < 0) { 74 throw new TypeError('n must be a non-negative number'); 75 } 76 this._maxListeners = n; 77 return this; 78 } 79 80 getMaxListeners(): number { 81 return this._maxListeners; 82 } 83 84 listenerCount(eventName: string): number { 85 const listeners = this._events[eventName]; 86 return listeners ? listeners.length : 0; 87 } 88 89 eventNames(): string[] { 90 return Object.keys(this._events); 91 } 92 93 listeners(eventName: string): Listener[] { 94 const listeners = this._events[eventName]; 95 return listeners ? listeners.slice() : []; 96 } 97 98 prependListener(eventName: string, listener: Listener): this { 99 return this._addListener(eventName, listener, false, true); 100 } 101 102 prependOnceListener(eventName: string, listener: Listener): this { 103 return this._addListener(eventName, listener, true, true); 104 } 105 106 // 私有方法 107 private _addListener(eventName: string, listener: Listener, once: boolean, prepend: boolean = false): this { 108 if (typeof listener !== 'function') { 109 throw new TypeError('listener must be a function'); 110 } 111 112 if (!this._events[eventName]) { 113 this._events[eventName] = []; 114 } 115 116 const listeners = this._events[eventName]; 117 118 // 检查最大监听器限制 119 if (listeners.length >= this._maxListeners && this._maxListeners !== 0) { 120 console.warn(`MaxListenersExceededWarning for event ${eventName}`); 121 } 122 123 let listenerToAdd: Listener = listener; 124 125 if (once) { 126 const onceWrapper: OnceWrapper = (...args: any[]) => { 127 listener.apply(this, args); 128 onceWrapper._once = true; 129 }; 130 onceWrapper._originalListener = listener; 131 listenerToAdd = onceWrapper; 132 } 133 134 if (prepend) { 135 listeners.unshift(listenerToAdd); 136 } else { 137 listeners.push(listenerToAdd); 138 } 139 140 return this; 141 } 142 143 private _removeListener(eventName: string, listener: Listener): this { 144 const listeners = this._events[eventName]; 145 if (!listeners) return this; 146 147 let index = listeners.indexOf(listener); 148 149 // 如果没找到,尝试查找原始监听器 150 if (index === -1) { 151 for (let i = 0; i < listeners.length; i++) { 152 const current = listeners[i] as OnceWrapper; 153 if (current._originalListener === listener) { 154 index = i; 155 break; 156 } 157 } 158 } 159 160 if (index > -1) { 161 listeners.splice(index, 1); 162 if (listeners.length === 0) { 163 delete this._events[eventName]; 164 } 165 } 166 167 return this; 168 } 169} 170
四、测试用例
4.1 基础功能测试
1console.log("=== EventEmitter 基础功能测试 ==="); 2 3const emitter = new EventEmitter(); 4 5// 测试1: 基本订阅和触发 6let test1Count = 0; 7emitter.on("test1", (data) => { 8 console.log("测试1 - 收到数据:", data); 9 test1Count++; 10}); 11 12emitter.emit("test1", "Hello"); // 测试1 - 收到数据: Hello 13emitter.emit("test1", "World"); // 测试1 - 收到数据: World 14console.log(`测试1 - 调用次数: ${test1Count}`); // 测试1 - 调用次数: 2 15 16// 测试2: 多个监听器 17let test2Result = []; 18emitter.on("test2", (data) => { 19 test2Result.push([`listener1: ${data}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md)); 20}); 21emitter.on("test2", (data) => { 22 test2Result.push([`listener2: ${data.toUpperCase()}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md)); 23}); 24 25emitter.emit("test2", "hello"); 26console.log("测试2 - 多个监听器:", test2Result); // 测试2 - 多个监听器: [ 'listener1: hello', 'listener2: HELLO' ] 27 28// 测试3: 取消订阅 29let test3Count = 0; 30const test3Listener = () => { 31 test3Count++; 32 console.log("测试3 - 监听器被调用"); 33}; 34 35emitter.on("test3", test3Listener); 36emitter.emit("test3"); // 测试3 - 监听器被调用 37emitter.off("test3", test3Listener); 38emitter.emit("test3"); // 不调用 39console.log(`测试3 - 最终调用次数: ${test3Count}`); // 测试3 - 最终调用次数: 1 40 41// 测试4: once 方法 42let test4Count = 0; 43emitter.once("test4", () => { 44 test4Count++; 45 console.log("测试4 - once 监听器被调用"); 46}); 47 48emitter.emit("test4"); // 测试4 - once 监听器被调用 49emitter.emit("test4"); // 测试4 - once 监听器被调用 50emitter.emit("test4"); // 不调用 51console.log(`测试4 - once 调用次数: ${test4Count}`); // 测试4 - once 调用次数: 2 52 53// 测试5: 错误处理 54let errorCaught = false; 55emitter.on("error", (error) => { 56 errorCaught = true; 57 console.log("测试5 - 捕获到错误:", error.message); 58}); 59 60emitter.on("test5", () => { 61 throw new Error("测试错误"); // 测试5 - 捕获到错误: 测试错误 62}); 63 64emitter.emit("test5"); 65console.log(`测试5 - 错误是否被捕获: ${errorCaught}`); // 测试5 - 错误是否被捕获: true 66
4.2 高级功能测试
1console.log("\n=== EventEmitter 高级功能测试 ==="); 2 3const emitter2 = new EventEmitter(); 4 5// 测试6: prependListener 方法 6let test6Order = []; 7emitter2.on("test6", () => test6Order.push("normal1")); 8emitter2.on("test6", () => test6Order.push("normal2")); 9emitter2.prependListener("test6", () => test6Order.push("prepended")); 10 11emitter2.emit("test6"); 12console.log("测试6 - 监听器顺序:", test6Order); 13// 测试6 - 监听器顺序: [ 'prepended', 'normal1', 'normal2' ] 14 15// 测试7: 最大监听器限制 16emitter2.setMaxListeners(2); 17console.log(`测试7 - 最大监听器数: ${emitter2.getMaxListeners()}`); // 测试7 - 最大监听器数: 2 18 19emitter2.on("test7", () => {}); 20emitter2.on("test7", () => {}); 21// 第三个应该触发警告 22emitter2.on("test7", () => {}); 23 24// 测试8: 获取监听器信息 25emitter2.on("test8", () => {}); 26emitter2.on("test8", () => {}); 27emitter2.once("test8", () => {}); 28 29console.log(`测试8 - 监听器数量: ${emitter2.listenerCount("test8")}`); // 3 30console.log(`测试8 - 监听器数组长度: ${emitter2.listeners("test8").length}`); // 3 31console.log(`测试8 - 事件名称: ${emitter2.eventNames()}`); // ['test6', 'test7', 'test8'] 32 33// 测试9: removeAllListeners 34emitter2.removeAllListeners("test8"); 35console.log(`测试9 - 移除后监听器数量: ${emitter2.listenerCount("test8")}`); // 0 36 37// 测试10: 链式调用 38emitter2 39 .on("test10", () => console.log("测试10 - 链式调用1")) 40 .on("test10", () => console.log("测试10 - 链式调用2")) 41 .emit("test10"); 42
4.3 边界情况测试
1console.log('\n=== EventEmitter 边界情况测试 ==='); 2 3const emitter3 = new EventEmitter(); 4 5// 测试11: 重复添加相同监听器 6let test11Count = 0; 7const test11Listener = () => test11Count++; 8 9emitter3.on('test11', test11Listener); 10emitter3.on('test11', test11Listener); // 重复添加 11 12emitter3.emit('test11'); 13console.log(`测试11 - 重复监听器调用次数: ${test11Count}`); // 2 14 15// 测试12: 移除不存在的监听器 16const fakeListener = () => {}; 17emitter3.off('nonexistent', fakeListener); // 应该不报错 18 19// 测试13: 触发没有监听器的事件 20const result = emitter3.emit('nonexistent'); 21console.log(`测试13 - 触发无监听器事件返回值: ${result}`); // false 22 23// 测试14: 错误事件处理 24try { 25 emitter3.emit('error', new Error('未处理的错误')); 26} catch (error) { 27 console.log(`测试14 - 捕获未处理的错误: ${error.message}`); 28} 29 30// 测试15: once 监听器移除后再次触发 31let test15Count = 0; 32const test15Listener = () => { 33 test15Count++; 34 console.log(`测试15 - 第 ${test15Count} 次调用`); 35}; 36 37const removeOnce = emitter3.once('test15', test15Listener); 38emitter3.emit('test15'); // 调用 39removeOnce(); // 手动移除 40emitter3.emit('test15'); // 不调用 41console.log(`测试15 - 最终调用次数: ${test15Count}`); // 1 42
4.4 性能测试
1console.log('\n=== EventEmitter 性能测试 ==='); 2 3const performanceEmitter = new EventEmitter(); 4const iterations = 100000; 5 6// 准备测试数据 7const listeners = []; 8for (let i = 0; i < 100; i++) { 9 listeners.push(() => {}); 10} 11 12// 测试添加监听器的性能 13console.time('添加监听器'); 14for (let i = 0; i < iterations; i++) { 15 performanceEmitter.on('performance', listeners[i % listeners.length]); 16} 17console.timeEnd('添加监听器'); 18 19console.log(`添加后监听器数量: ${performanceEmitter.listenerCount('performance')}`); 20 21// 测试触发事件的性能 22console.time('触发事件'); 23for (let i = 0; i < iterations; i++) { 24 performanceEmitter.emit('performance', i); 25} 26console.timeEnd('触发事件'); 27 28// 测试移除监听器的性能 29console.time('移除监听器'); 30for (let i = 0; i < iterations; i++) { 31 performanceEmitter.off('performance', listeners[i % listeners.length]); 32} 33console.timeEnd('移除监听器'); 34 35console.log(`移除后监听器数量: ${performanceEmitter.listenerCount('performance')}`); 36
五、EventEmitter的核心原理分析
51 数据结构设计
1// EventEmitter 的核心数据结构 2class EventEmitter { 3 constructor() { 4 // 使用普通对象而不是 Map 的原因: 5 // 1. 在 V8 中,普通对象性能更好 6 // 2. 事件名通常是字符串,适合作为对象键 7 // 3. Object.create(null) 创建没有原型的对象,避免原型污染 8 this._events = Object.create(null); 9 10 // 每个事件对应一个监听器数组 11 // { 12 // 'event1': [listener1, listener2, ...], 13 // 'event2': [listener3, listener4, ...] 14 // } 15 } 16} 17
5.2 once 方法的实现原理
1// once 方法的实现细节 2once(eventName, listener) { 3 // 创建包装函数 4 const onceWrapper = (...args) => { 5 // 1. 执行原始监听器 6 listener.apply(this, args); 7 8 // 2. 标记为已执行 9 onceWrapper._once = true; 10 11 // 3. 在 emit 中检测到这个标记后会移除监听器 12 }; 13 14 // 保存原始监听器引用,用于 off 方法 15 onceWrapper._originalListener = listener; 16 17 // 添加到监听器数组 18 this._addListener(eventName, onceWrapper, false); 19 20 return this; 21} 22 23// emit 方法中处理 once 监听器 24emit(eventName, ...args) { 25 // ... 26 for (const listener of listenersCopy) { 27 // 检查是否为 once 包装函数 28 if (listener._once) { 29 // 移除监听器 30 this._removeListener(eventName, listener); 31 } 32 // ... 33 } 34 // ... 35} 36
5.3 内存管理策略
1// 避免内存泄漏的实现 2class SafeEventEmitter extends EventEmitter { 3 constructor() { 4 super(); 5 // 跟踪所有订阅,便于清理 6 this._subscriptions = new WeakMap(); 7 } 8 9 safeOn(eventName, listener, context = null) { 10 // 绑定上下文 11 const boundListener = context ? listener.bind(context) : listener; 12 13 // 存储元数据 14 const meta = { 15 eventName, 16 originalListener: listener, 17 boundListener, 18 unsubscribe: () => this.off(eventName, boundListener) 19 }; 20 21 // 使用 WeakMap 存储,不会阻止垃圾回收 22 this._subscriptions.set(listener, meta); 23 24 // 添加监听器 25 this.on(eventName, boundListener); 26 27 // 返回增强的取消订阅函数 28 return () => { 29 this.off(eventName, boundListener); 30 this._subscriptions.delete(listener); 31 }; 32 } 33} 34
六、常见面试题实现
6.1 实现一个简单的 EventBus
1// 全局事件总线(类似 Vue 中的 EventBus) 2class EventBus { 3 constructor() { 4 this._events = Object.create(null); 5 } 6 7 // 单例模式 8 static getInstance() { 9 if (!EventBus._instance) { 10 EventBus._instance = new EventBus(); 11 } 12 return EventBus._instance; 13 } 14 15 $on(event, callback) { 16 if (!this._events[event]) { 17 this._events[event] = []; 18 } 19 this._events[event].push(callback); 20 } 21 22 $emit(event, ...args) { 23 const callbacks = this._events[event]; 24 if (!callbacks) return; 25 26 // 使用 slice 创建副本,避免迭代时修改数组 27 callbacks.slice().forEach(callback => { 28 try { 29 callback(...args); 30 } catch (error) { 31 console.error(`EventBus error in ${event}:`, error); 32 } 33 }); 34 } 35 36 $off(event, callback) { 37 if (!this._events[event]) return; 38 39 if (callback) { 40 const index = this._events[event].indexOf(callback); 41 if (index > -1) { 42 this._events[event].splice(index, 1); 43 } 44 } else { 45 delete this._events[event]; 46 } 47 } 48 49 $once(event, callback) { 50 const onceWrapper = (...args) => { 51 callback(...args); 52 this.$off(event, onceWrapper); 53 }; 54 this.$on(event, onceWrapper); 55 } 56} 57 58// 使用示例 59const bus = EventBus.getInstance(); 60 61// 组件 A 62bus.$on('user-login', (user) => { 63 console.log('组件A: 用户登录', user.name); 64}); 65 66// 组件 B 67bus.$on('user-login', (user) => { 68 console.log('组件B: 更新用户信息', user.id); 69}); 70 71// 登录成功后 72bus.$emit('user-login', { id: 1, name: 'Alice' }); 73
6.2 实现带命名空间的事件系统
1class NamespacedEventEmitter { 2 constructor() { 3 this._events = Object.create(null); 4 this._separator = ':'; 5 } 6 7 // 解析事件名,支持命名空间 8 _parseEvent(eventString) { 9 const parts = eventString.split(this._separator); 10 if (parts.length === 1) { 11 return { namespace: null, event: parts[0] }; 12 } 13 return { namespace: parts[0], event: parts.slice(1).join(this._separator) }; 14 } 15 16 // 生成完整的事件键 17 _getEventKey(namespace, event) { 18 return namespace ? `${namespace}${this._separator}${event}` : event; 19 } 20 21 on(eventString, listener) { 22 const { namespace, event } = this._parseEvent(eventString); 23 const eventKey = this._getEventKey(namespace, event); 24 25 if (!this._events[eventKey]) { 26 this._events[eventKey] = []; 27 } 28 29 this._events[eventKey].push({ 30 listener, 31 namespace, 32 event 33 }); 34 35 return () => this.off(eventString, listener); 36 } 37 38 emit(eventString, ...args) { 39 const { namespace, event } = this._parseEvent(eventString); 40 41 // 收集所有匹配的监听器 42 const listenersToCall = []; 43 44 // 如果指定了命名空间,只触发该命名空间的事件 45 if (namespace) { 46 const eventKey = this._getEventKey(namespace, event); 47 if (this._events[eventKey]) { 48 listenersToCall.push(...this._events[eventKey]); 49 } 50 } else { 51 // 如果没有指定命名空间,触发所有匹配的事件 52 for (const eventKey in this._events) { 53 const listeners = this._events[eventKey]; 54 for (const listenerInfo of listeners) { 55 if (listenerInfo.event === event) { 56 listenersToCall.push(listenerInfo); 57 } 58 } 59 } 60 } 61 62 // 执行监听器 63 listenersToCall.forEach(({ listener }) => { 64 try { 65 listener(...args); 66 } catch (error) { 67 console.error(`Error in ${eventString}:`, error); 68 } 69 }); 70 } 71 72 off(eventString, listener) { 73 const { namespace, event } = this._parseEvent(eventString); 74 75 if (namespace) { 76 const eventKey = this._getEventKey(namespace, event); 77 if (this._events[eventKey]) { 78 const listeners = this._events[eventKey]; 79 const index = listeners.findIndex(item => item.listener === listener); 80 if (index > -1) { 81 listeners.splice(index, 1); 82 if (listeners.length === 0) { 83 delete this._events[eventKey]; 84 } 85 } 86 } 87 } else { 88 // 移除所有命名空间下的事件 89 for (const eventKey in this._events) { 90 const listeners = this._events[eventKey]; 91 for (let i = listeners.length - 1; i >= 0; i--) { 92 if (listeners[i].listener === listener && listeners[i].event === event) { 93 listeners.splice(i, 1); 94 } 95 } 96 if (listeners.length === 0) { 97 delete this._events[eventKey]; 98 } 99 } 100 } 101 } 102} 103 104// 使用示例 105const nsEmitter = new NamespacedEventEmitter(); 106 107nsEmitter.on('user:login', () => console.log('用户模块: 登录')); 108nsEmitter.on('admin:login', () => console.log('管理员模块: 登录')); 109nsEmitter.on('login', () => console.log('全局: 登录')); 110 111nsEmitter.emit('user:login'); // 只输出: 用户模块: 登录 112nsEmitter.emit('admin:login'); // 只输出: 管理员模块: 登录 113nsEmitter.emit('login'); // 输出所有 114
6.3 实现支持异步监听器的 EventEmitter
1class AsyncEventEmitter extends EventEmitter { 2 /** 3 * 异步触发事件,等待所有监听器完成 4 */ 5 async emitAsync(eventName, ...args) { 6 const listeners = this.listeners(eventName); 7 if (listeners.length === 0) { 8 return; 9 } 10 11 // 并行执行所有监听器 12 const promises = listeners.map(async (listener) => { 13 try { 14 const result = listener(...args); 15 // 如果监听器返回 Promise,等待它完成 16 if (result && typeof result.then === 'function') { 17 await result; 18 } 19 } catch (error) { 20 if (eventName !== 'error') { 21 await this.emitAsync('error', error); 22 } else { 23 throw error; 24 } 25 } 26 }); 27 28 await Promise.all(promises); 29 } 30 31 /** 32 * 顺序执行监听器(一个接一个) 33 */ 34 async emitSeries(eventName, ...args) { 35 const listeners = this.listeners(eventName); 36 37 for (const listener of listeners) { 38 try { 39 const result = listener(...args); 40 // 如果监听器返回 Promise,等待它完成 41 if (result && typeof result.then === 'function') { 42 await result; 43 } 44 } catch (error) { 45 if (eventName !== 'error') { 46 await this.emitSeries('error', error); 47 } else { 48 throw error; 49 } 50 } 51 } 52 } 53} 54 55// 使用示例 56const asyncEmitter = new AsyncEventEmitter(); 57 58asyncEmitter.on('process', async (data) => { 59 await new Promise(resolve => setTimeout(resolve, 100)); 60 console.log('处理完成:', data); 61}); 62 63asyncEmitter.on('process', async (data) => { 64 console.log('第二个监听器:', data); 65}); 66 67asyncEmitter.emitAsync('process', '测试数据'); 68
6.5 实现支持优先级的 EventEmitter
1class PriorityEventEmitter { 2 constructor() { 3 this._events = Object.create(null); 4 this._defaultPriority = 0; 5 } 6 7 on(eventName, listener, priority = this._defaultPriority) { 8 if (!this._events[eventName]) { 9 this._events[eventName] = []; 10 } 11 12 const listeners = this._events[eventName]; 13 listeners.push({ listener, priority }); 14 15 // 按优先级排序(数字越小优先级越高) 16 listeners.sort((a, b) => a.priority - b.priority); 17 18 return () => this.off(eventName, listener); 19 } 20 21 emit(eventName, ...args) { 22 const listeners = this._events[eventName]; 23 if (!listeners) return; 24 25 // 遍历已排序的监听器 26 for (const { listener } of listeners) { 27 try { 28 const result = listener(...args); 29 // 如果监听器返回 false,停止后续监听器的执行 30 if (result === false) { 31 break; 32 } 33 } catch (error) { 34 console.error(`Error in ${eventName}:`, error); 35 } 36 } 37 } 38 39 off(eventName, listener) { 40 const listeners = this._events[eventName]; 41 if (!listeners) return this; 42 43 const index = listeners.findIndex(item => item.listener === listener); 44 if (index > -1) { 45 listeners.splice(index, 1); 46 if (listeners.length === 0) { 47 delete this._events[eventName]; 48 } 49 } 50 51 return this; 52 } 53} 54 55// 使用示例 56const priorityEmitter = new PriorityEventEmitter(); 57 58priorityEmitter.on('process', () => console.log('优先级 10'), 10); 59priorityEmitter.on('process', () => console.log('优先级 0'), 0); 60priorityEmitter.on('process', () => console.log('优先级 5'), 5); 61 62priorityEmitter.emit('process'); 63// 输出顺序: 优先级 0, 优先级 5, 优先级 10 64
七、实际应用场景
7.1 在 Vue 中实现组件通信
1// 全局事件总线 2const EventBus = new EventEmitter(); 3 4// 在 Vue 组件中使用 5// ComponentA.vue 6export default { 7 mounted() { 8 EventBus.on('user-updated', this.handleUserUpdate); 9 }, 10 methods: { 11 handleUserUpdate(user) { 12 console.log('用户更新:', user); 13 this.user = user; 14 } 15 }, 16 beforeDestroy() { 17 EventBus.off('user-updated', this.handleUserUpdate); 18 } 19}; 20 21// ComponentB.vue 22export default { 23 methods: { 24 updateUser() { 25 EventBus.emit('user-updated', { id: 1, name: 'John' }); 26 } 27 } 28}; 29 30// 或者在 Vue 原型上添加 31Vue.prototype.$bus = new EventEmitter(); 32 33// 在组件中使用 34this.$bus.on('event', handler); 35this.$bus.emit('event', data); 36this.$bus.off('event', handler); 37
7.2 在 Express 中实现事件驱动架构
1const express = require('express'); 2const EventEmitter = require('./EventEmitter'); 3 4class AppEvents extends EventEmitter { 5 constructor() { 6 super(); 7 this.setupEvents(); 8 } 9 10 setupEvents() { 11 // 定义应用级别事件 12 this.on('user:registered', (user) => { 13 console.log('新用户注册:', user.email); 14 // 发送欢迎邮件 15 // 创建用户目录 16 // 更新统计 17 }); 18 19 this.on('order:created', (order) => { 20 console.log('新订单:', order.id); 21 // 发送确认邮件 22 // 更新库存 23 // 通知物流 24 }); 25 26 this.on('error', (error, context) => { 27 console.error('应用错误:', error.message, context); 28 // 发送错误报告 29 // 记录到监控系统 30 }); 31 } 32} 33 34// 创建 Express 应用 35const appEvents = new AppEvents(); 36const app = express(); 37 38// 中间件:将事件发射器添加到请求对象 39app.use((req, res, next) => { 40 req.appEvents = appEvents; 41 next(); 42}); 43 44// 路由处理 45app.post('/register', (req, res) => { 46 const user = createUser(req.body); 47 48 // 触发事件 49 req.appEvents.emit('user:registered', user); 50 51 res.json({ success: true, user }); 52}); 53 54app.post('/order', (req, res) => { 55 const order = createOrder(req.body); 56 57 // 触发事件 58 req.appEvents.emit('order:created', order); 59 60 res.json({ success: true, order }); 61}); 62 63// 错误处理中间件 64app.use((error, req, res, next) => { 65 // 触发错误事件 66 req.appEvents.emit('error', error, { 67 url: req.url, 68 method: req.method, 69 userId: req.user?.id 70 }); 71 72 res.status(500).json({ error: 'Internal server error' }); 73}); 74
7.3 实现简单的状态管理
1class ObservableStore { 2 constructor(initialState = {}) { 3 this._state = initialState; 4 this._prevState = null; 5 this._emitter = new EventEmitter(); 6 } 7 8 // 获取当前状态 9 getState() { 10 return this._state; 11 } 12 13 // 设置状态 14 setState(updates) { 15 this._prevState = { ...this._state }; 16 this._state = { ...this._state, ...updates }; 17 18 // 触发状态变化事件 19 this._emitter.emit('state:changed', this._state, this._prevState); 20 21 // 触发特定属性的变化事件 22 Object.keys(updates).forEach(key => { 23 this._emitter.emit(`state:${key}:changed`, updates[key], this._prevState[key]); 24 }); 25 } 26 27 // 订阅状态变化 28 subscribe(callback) { 29 return this._emitter.on('state:changed', callback); 30 } 31 32 // 订阅特定状态变化 33 subscribeTo(key, callback) { 34 return this._emitter.on(`state:${key}:changed`, callback); 35 } 36 37 // 批量更新 38 batchUpdate(updater) { 39 const updates = updater(this._state); 40 this.setState(updates); 41 } 42} 43 44// 使用示例 45const store = new ObservableStore({ 46 user: null, 47 theme: 'light', 48 notifications: [] 49}); 50 51// 订阅状态变化 52store.subscribe((newState, oldState) => { 53 console.log('状态变化:', newState); 54}); 55 56// 订阅特定状态变化 57store.subscribeTo('theme', (newTheme, oldTheme) => { 58 console.log('主题变化:', oldTheme, '->', newTheme); 59 document.body.setAttribute('data-theme', newTheme); 60}); 61 62// 更新状态 63store.setState({ theme: 'dark' }); 64store.setState({ user: { id: 1, name: 'Alice' } }); 65 66// 批量更新 67store.batchUpdate(state => ({ 68 notifications: [...state.notifications, '新消息'], 69 theme: 'dark' 70})); 71
八、性能优化和注意事项
8.1 内存泄漏预防
1// 安全的 EventEmitter,自动清理订阅 2class SafeEventEmitter extends EventEmitter { 3 constructor() { 4 super(); 5 // 使用 WeakRef 跟踪组件引用 6 this._componentRefs = new WeakMap(); 7 } 8 9 // 为组件绑定事件,自动清理 10 bindToComponent(component, eventName, listener) { 11 // 创建绑定函数 12 const boundListener = listener.bind(component); 13 14 // 添加监听器 15 this.on(eventName, boundListener); 16 17 // 存储引用 18 if (!this._componentRefs.has(component)) { 19 this._componentRefs.set(component, []); 20 } 21 this._componentRefs.get(component).push({ eventName, listener: boundListener }); 22 23 // 返回清理函数 24 return () => { 25 this.off(eventName, boundListener); 26 const refs = this._componentRefs.get(component); 27 if (refs) { 28 const index = refs.findIndex(ref => 29 ref.eventName === eventName && ref.listener === boundListener 30 ); 31 if (index > -1) { 32 refs.splice(index, 1); 33 } 34 } 35 }; 36 } 37 38 // 清理组件的所有事件 39 cleanupComponent(component) { 40 const refs = this._componentRefs.get(component); 41 if (refs) { 42 refs.forEach(({ eventName, listener }) => { 43 this.off(eventName, listener); 44 }); 45 this._componentRefs.delete(component); 46 } 47 } 48} 49 50// 使用示例 51class Component { 52 constructor(emitter) { 53 this.emitter = emitter; 54 this._cleanupFns = []; 55 } 56 57 setupEvents() { 58 // 绑定事件,自动管理生命周期 59 const cleanup1 = this.emitter.bindToComponent( 60 this, 61 'data', 62 this.handleData.bind(this) 63 ); 64 this._cleanupFns.push(cleanup1); 65 66 const cleanup2 = this.emitter.bindToComponent( 67 this, 68 'error', 69 this.handleError.bind(this) 70 ); 71 this._cleanupFns.push(cleanup2); 72 } 73 74 handleData(data) { 75 console.log('处理数据:', data); 76 } 77 78 handleError(error) { 79 console.error('处理错误:', error); 80 } 81 82 destroy() { 83 // 清理所有事件 84 this._cleanupFns.forEach(fn => fn()); 85 this._cleanupFns = []; 86 87 // 或者使用自动清理 88 this.emitter.cleanupComponent(this); 89 } 90} 91
8.2 性能优化技巧
1// 高性能 EventEmitter 2class HighPerformanceEventEmitter { 3 constructor() { 4 // 使用空对象作为原型,避免原型链查找 5 this._events = Object.create(null); 6 // 缓存空数组,避免频繁创建 7 this._emptyArray = Object.freeze([]); 8 } 9 10 on(eventName, listener) { 11 if (!this._events[eventName]) { 12 // 预分配数组空间 13 this._events[eventName] = []; 14 } 15 16 this._events[eventName].push(listener); 17 return this; 18 } 19 20 emit(eventName, ...args) { 21 // 快速路径:没有监听器 22 const listeners = this._events[eventName]; 23 if (!listeners) return false; 24 25 // 使用 for 循环而不是 forEach,性能更好 26 for (let i = 0, len = listeners.length; i < len; i++) { 27 try { 28 listeners[i].apply(this, args); 29 } catch (error) { 30 // 错误处理 31 if (eventName !== 'error') { 32 const errorListeners = this._events.error; 33 if (errorListeners) { 34 // 避免递归调用 35 for (let j = 0; j < errorListeners.length; j++) { 36 try { 37 errorListeners[j].call(this, error); 38 } catch (e) { 39 // 忽略错误处理函数中的错误 40 } 41 } 42 } 43 } 44 } 45 } 46 47 return true; 48 } 49 50 off(eventName, listener) { 51 const listeners = this._events[eventName]; 52 if (!listeners) return this; 53 54 // 从后向前遍历,避免数组移动 55 for (let i = listeners.length - 1; i >= 0; i--) { 56 if (listeners[i] === listener) { 57 listeners.splice(i, 1); 58 break; 59 } 60 } 61 62 // 如果没有监听器了,删除属性(让 V8 优化) 63 if (listeners.length === 0) { 64 delete this._events[eventName]; 65 } 66 67 return this; 68 } 69 70 // 批量操作优化 71 emitMany(eventNames, ...args) { 72 const results = []; 73 for (const eventName of eventNames) { 74 results.push(this.emit(eventName, ...args)); 75 } 76 return results; 77 } 78} 79
8,3 调试和监控
1// 可监控的 EventEmitter 2class MonitoredEventEmitter extends EventEmitter { 3 constructor(options = {}) { 4 super(); 5 this._monitoring = { 6 enabled: options.enabled !== false, 7 emitCount: 0, 8 listenerCount: 0, 9 eventStats: new Map(), 10 errorStats: new Map(), 11 slowListeners: [] 12 }; 13 14 // 性能监控阈值(毫秒) 15 this._slowThreshold = options.slowThreshold || 100; 16 } 17 18 // 重写 emit 方法以收集监控数据 19 emit(eventName, ...args) { 20 if (!this._monitoring.enabled) { 21 return super.emit(eventName, ...args); 22 } 23 24 this._monitoring.emitCount++; 25 26 // 更新事件统计 27 const eventStat = this._monitoring.eventStats.get(eventName) || { 28 count: 0, 29 lastEmitted: null, 30 avgDuration: 0, 31 maxDuration: 0 32 }; 33 34 eventStat.count++; 35 eventStat.lastEmitted = new Date(); 36 37 const startTime = performance.now(); 38 const result = super.emit(eventName, ...args); 39 const duration = performance.now() - startTime; 40 41 // 更新性能统计 42 eventStat.avgDuration = 43 (eventStat.avgDuration * (eventStat.count - 1) + duration) / eventStat.count; 44 eventStat.maxDuration = Math.max(eventStat.maxDuration, duration); 45 46 this._monitoring.eventStats.set(eventName, eventStat); 47 48 // 记录慢监听器 49 if (duration > this._slowThreshold) { 50 this._monitoring.slowListeners.push({ 51 eventName, 52 duration, 53 timestamp: new Date(), 54 args: args.slice(0, 3) // 只记录前三个参数 55 }); 56 57 // 保持慢监听器记录的数量 58 if (this._monitoring.slowListeners.length > 100) { 59 this._monitoring.slowListeners.shift(); 60 } 61 } 62 63 return result; 64 } 65 66 // 获取监控数据 67 getMonitoringData() { 68 return { 69 ...this._monitoring, 70 currentListeners: this._getListenersCount(), 71 eventNames: this.eventNames(), 72 timestamp: new Date() 73 }; 74 } 75 76 // 重置监控数据 77 resetMonitoring() { 78 this._monitoring = { 79 enabled: true, 80 emitCount: 0, 81 listenerCount: 0, 82 eventStats: new Map(), 83 errorStats: new Map(), 84 slowListeners: [] 85 }; 86 } 87 88 // 生成监控报告 89 generateReport() { 90 const data = this.getMonitoringData(); 91 92 console.log('=== EventEmitter 监控报告 ==='); 93 console.log([`运行时间: ${data.timestamp.toISOString()}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md)); 94 console.log([`总触发次数: ${data.emitCount}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md)); 95 console.log([`活跃事件数量: ${data.eventNames.length}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md)); 96 97 console.log('\n事件统计:'); 98 for (const [eventName, stat] of data.eventStats) { 99 console.log(` ${eventName}:`); 100 console.log([` 触发次数: ${stat.count}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.stat.md)); 101 console.log([` 平均耗时: ${stat.avgDuration.toFixed(2)}ms`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.stat.md)); 102 console.log([` 最大耗时: ${stat.maxDuration.toFixed(2)}ms`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.stat.md)); 103 console.log([` 最后触发: ${stat.lastEmitted.toISOString()}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.stat.md)); 104 } 105 106 if (data.slowListeners.length > 0) { 107 console.log('\n慢监听器警告:'); 108 data.slowListeners.forEach((item, index) => { 109 console.log(` ${index + 1}. ${item.eventName} - ${item.duration.toFixed(2)}ms`); 110 }); 111 } 112 113 return data; 114 } 115 116 // 私有方法:获取监听器计数 117 _getListenersCount() { 118 const result = {}; 119 for (const eventName in this._events) { 120 result[eventName] = this._events[eventName].length; 121 } 122 return result; 123 } 124} 125
九、总结与最佳实践
9.1 核心要点总结
- 发布订阅模式的核心: 通过事件中心解耦发布者和订阅者
- EventEmitter的实现要点:
- 使用合适的数据结构存储事件和监听器
- 正确处理 once 监听器
- 实现错误处理机制
- 支持链式调用
- 内存管理: 及时清理监听器, 避免内存泄漏
- 性能考虑: 选择合适的数据结构和算法
9.2 最佳实现
- 命名规范:
1// 好的命名 2emitter.on('user:login', handler); 3emitter.on('order:created', handler); 4 5// 不好的命名 6emitter.on('login', handler); 7emitter.on('newOrder', handler); 8
- 错误处理:
1// 总是监听 error 事件 2emitter.on('error', (error) => { 3 console.error('EventEmitter error:', error); 4 // 发送到错误监控系统 5}); 6 7// 或者在监听器内部处理错误 8emitter.on('data', (data) => { 9 try { 10 processData(data); 11 } catch (error) { 12 console.error('处理数据时出错:', error); 13 emitter.emit('error', error); 14 } 15}); 16
- 资源清理:
1class Component { 2 constructor() { 3 this._cleanupFns = []; 4 } 5 6 setupEvents() { 7 const cleanup1 = emitter.on('event1', this.handler1.bind(this)); 8 this._cleanupFns.push(cleanup1); 9 10 const cleanup2 = emitter.once('event2', this.handler2.bind(this)); 11 this._cleanupFns.push(cleanup2); 12 } 13 14 destroy() { 15 // 清理所有事件监听器 16 this._cleanupFns.forEach(fn => fn()); 17 this._cleanupFns = []; 18 } 19} 20
9.3 使用建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单组件通信 | 基础 EventEmitter | 轻量、简单 |
| 大型应用状态管理 | Observable Store | 结构化、可预测 |
| 异步任务协调 | AsyncEventEmitter | 更好的异步支持 |
| 性能敏感场景 | HighPerformanceEventEmitter | 优化过的实现 |
| 需要监控调试 | MonitoredEventEmitter | 内置监控功能 |
结语
通过手写 EventEmitter,我们不仅掌握了发布订阅模式的实现原理,更重要的是理解了事件驱动编程的核心思想。EventEmitter 虽然简单,但其设计思想在现代前端框架、Node.js 后端系统以及各种复杂应用中都有广泛的应用。
记住,好的事件系统应该:
- ✅ 职责清晰:事件中心只负责转发消息
- ✅ 性能优秀:高频事件触发时表现良好
- ✅ 易于调试:有良好的监控和错误处理
- ✅ 内存安全:避免内存泄漏和资源浪费
延伸阅读:
希望这篇经过严格测试的博客能帮助你深入理解 EventEmitter!如果有任何问题或建议,欢迎讨论交流。
《手写 EventEmitter:深入理解发布订阅模式》 是转载文章,点击查看原文。