MST

星途 面试题库

面试题:JavaScript 扩展 Node 事件与 EventEmitter 的高级应用与优化

假设你在一个大型 Node.js 项目中,遇到了 EventEmitter 事件过多导致内存泄漏的问题,你会如何排查和优化?请阐述具体的思路和可能用到的工具,并且如果要对 EventEmitter 进行扩展,实现事件优先级的功能,你会怎么做?
26.8万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

排查 EventEmitter 事件过多导致内存泄漏的思路与工具

  1. 思路
    • 找出事件监听过多的模块:遍历项目代码,统计每个模块中 EventEmitter 实例添加的监听器数量。可以通过在关键位置(如添加监听器的代码处)添加日志记录,记录每个监听器添加的位置、事件类型和监听器函数。
    • 检查监听器是否正确移除:查看是否存在添加了监听器但未正确移除的情况。特别是在异步操作完成后,确保相关监听器被移除。例如,在 http 服务器的请求处理函数中,如果为请求对象添加了监听器,当请求处理完成时应移除监听器。
    • 分析事件触发频率:确定某些事件是否被过度触发。这可能是由于业务逻辑错误导致的,比如在循环中频繁触发不必要的事件。
  2. 工具
    • Node.js 内置的 events 模块方法EventEmitter.listenerCount(emitter, eventName) 方法可以获取指定事件的监听器数量,通过在关键位置调用此方法并记录结果,有助于定位事件监听过多的区域。
    • Chrome DevTools:使用 node --inspect 启动 Node.js 应用,然后在 Chrome 浏览器中打开 chrome://inspect,连接到 Node.js 进程。在 DevTools 的 Memory 面板中,可以进行堆快照分析,查看对象的引用关系,找出可能导致内存泄漏的 EventEmitter 实例及其监听器。

对 EventEmitter 进行扩展实现事件优先级的做法

  1. 自定义 EventEmitter 类
    • 继承 events.EventEmitter 类。
    const { EventEmitter } = require('events');
    
    class PriorityEventEmitter extends EventEmitter {
        constructor() {
            super();
            this.eventQueue = {};
        }
    }
    
  2. 添加按优先级触发事件的逻辑
    • 重写 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;
    };
    
  3. 重写 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