面试题答案
一键面试排查 EventEmitter 事件过多导致内存泄漏的思路与工具
- 思路
- 找出事件监听过多的模块:遍历项目代码,统计每个模块中
EventEmitter
实例添加的监听器数量。可以通过在关键位置(如添加监听器的代码处)添加日志记录,记录每个监听器添加的位置、事件类型和监听器函数。 - 检查监听器是否正确移除:查看是否存在添加了监听器但未正确移除的情况。特别是在异步操作完成后,确保相关监听器被移除。例如,在
http
服务器的请求处理函数中,如果为请求对象添加了监听器,当请求处理完成时应移除监听器。 - 分析事件触发频率:确定某些事件是否被过度触发。这可能是由于业务逻辑错误导致的,比如在循环中频繁触发不必要的事件。
- 找出事件监听过多的模块:遍历项目代码,统计每个模块中
- 工具
- Node.js 内置的
events
模块方法:EventEmitter.listenerCount(emitter, eventName)
方法可以获取指定事件的监听器数量,通过在关键位置调用此方法并记录结果,有助于定位事件监听过多的区域。 - Chrome DevTools:使用
node --inspect
启动 Node.js 应用,然后在 Chrome 浏览器中打开chrome://inspect
,连接到 Node.js 进程。在 DevTools 的Memory
面板中,可以进行堆快照分析,查看对象的引用关系,找出可能导致内存泄漏的EventEmitter
实例及其监听器。
- Node.js 内置的
对 EventEmitter 进行扩展实现事件优先级的做法
- 自定义 EventEmitter 类
- 继承
events.EventEmitter
类。
const { EventEmitter } = require('events'); class PriorityEventEmitter extends EventEmitter { constructor() { super(); this.eventQueue = {}; } }
- 继承
- 添加按优先级触发事件的逻辑
- 重写
on
方法,使其支持传入优先级参数。 - 使用一个对象来存储每个事件的监听器数组,每个监听器数组按照优先级排序。
PriorityEventEmitter.prototype.on = function (eventName, priority, listener) { if (!this.eventQueue[eventName]) { this.eventQueue[eventName] = []; } const entry = { priority, listener }; let inserted = false; for (let i = 0; i < this.eventQueue[eventName].length; i++) { if (this.eventQueue[eventName][i].priority < priority) { this.eventQueue[eventName].splice(i, 0, entry); inserted = true; break; } } if (!inserted) { this.eventQueue[eventName].push(entry); } return this; };
- 重写
- 重写
emit
方法- 按照优先级顺序触发监听器。
PriorityEventEmitter.prototype.emit = function (eventName, ...args) { const listeners = this.eventQueue[eventName]; if (listeners) { listeners.forEach(({ listener }) => { listener.apply(this, args); }); } return this.listenerCount(eventName) > 0; };
这样就扩展了 EventEmitter
,实现了事件优先级的功能。在使用时,可以像这样:
const emitter = new PriorityEventEmitter();
emitter.on('test', 2, () => console.log('Priority 2'));
emitter.on('test', 1, () => console.log('Priority 1'));
emitter.emit('test');
上述代码会先输出 Priority 2
,再输出 Priority 1
。