Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试官:说说Node中的EventEmitter? 如何实现一个EventEmitter? #166

Open
huihuiha opened this issue Jun 21, 2021 · 1 comment

Comments

@huihuiha
Copy link
Contributor

一、是什么

我们了解到,Node 采用了事件驱动机制,而EventEmitter 就是Node实现事件驱动的基础

EventEmitter的基础上,Node 几乎所有的模块都继承了这个类,这些模块拥有了自己的事件,可以绑定/触发监听器,实现了异步操作

Node.js 里面的许多对象都会分发事件,比如 fs.readStream 对象会在文件被打开的时候触发一个事件

这些产生事件的对象都是 events.EventEmitter 的实例,这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上

二、使用方法

Node events模块只提供了一个EventEmitter类,这个类实现了Node异步事件驱动架构的基本模式——观察者模式

在这种模式中,被观察者(主体)维护着一组其他对象派来(注册)的观察者,有新的对象对主体感兴趣就注册观察者,不感兴趣就取消订阅,主体有更新的话就依次通知观察者们

基本代码如下所示:

const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter()

function callback() {
    console.log('触发了event事件!')
}
myEmitter.on('event', callback)
myEmitter.emit('event')
myEmitter.removeListener('event', callback);

通过实例对象的on方法注册一个名为event的事件,通过emit方法触发该事件,而removeListener用于取消事件的监听

关于其常见的方法如下:

  • emitter.addListener/on(eventName, listener) :添加类型为 eventName 的监听事件到事件数组尾部
  • emitter.prependListener(eventName, listener):添加类型为 eventName 的监听事件到事件数组头部
  • emitter.emit(eventName[, ...args]):触发类型为 eventName 的监听事件
  • emitter.removeListener/off(eventName, listener):移除类型为 eventName 的监听事件
  • emitter.once(eventName, listener):添加类型为 eventName 的监听事件,以后只能执行一次并删除
  • emitter.removeAllListeners([eventName]): 移除全部类型为 eventName 的监听事件

三、实现过程

通过上面的方法了解,EventEmitter是一个构造函数,内部存在一个包含所有事件的对象

class EventEmitter {
    constructor() {
        this.events = {};
    }
}

其中events存放的监听事件的函数的结构如下:

{
  "event1": [f1,f2,f3]
  "event2": [f4,f5]
  ...
}

然后开始一步步实现实例方法,首先是emit,第一个参数为事件的类型,第二个参数开始为触发事件函数的参数,实现如下:

emit(type, ...args) {
    this.events[type].forEach((item) => {
        Reflect.apply(item, this, args);
    });
}

当实现了emit方法之后,然后实现onaddListenerprependListener这三个实例方法,都是添加事件监听触发函数,实现也是大同小异

on(type, handler) {
    if (!this.events[type]) {
        this.events[type] = [];
    }
    this.events[type].push(handler);
}

addListener(type,handler){
    this.on(type,handler)
}

prependListener(type, handler) {
    if (!this.events[type]) {
        this.events[type] = [];
    }
    this.events[type].unshift(handler);
}

紧接着就是实现事件监听的方法removeListener/on

removeListener(type, handler) {
    if (!this.events[type]) {
        return;
    }
    this.events[type] = this.events[type].filter(item => item !== handler);
}

off(type,handler){
    this.removeListener(type,handler)
}

最后再来实现once方法, 再传入事件监听处理函数的时候进行封装,利用闭包的特性维护当前状态,通过fired属性值判断事件函数是否执行过

once(type, handler) {
    this.on(type, this._onceWrap(type, handler, this));
  }

  _onceWrap(type, handler, target) {
    const state = { fired: false, handler, type , target};
    const wrapFn = this._onceWrapper.bind(state);
    state.wrapFn = wrapFn;
    return wrapFn;
  }

  _onceWrapper(...args) {
    if (!this.fired) {
      this.fired = true;
      Reflect.apply(this.handler, this.target, args);
      this.target.off(this.type, this.wrapFn);
    }
 }

完整代码如下:

class EventEmitter {
    constructor() {
        this.events = {};
    }

    on(type, handler) {
        if (!this.events[type]) {
            this.events[type] = [];
        }
        this.events[type].push(handler);
    }

    addListener(type,handler){
        this.on(type,handler)
    }

    prependListener(type, handler) {
        if (!this.events[type]) {
            this.events[type] = [];
        }
        this.events[type].unshift(handler);
    }

    removeListener(type, handler) {
        if (!this.events[type]) {
            return;
        }
        this.events[type] = this.events[type].filter(item => item !== handler);
    }

    off(type,handler){
        this.removeListener(type,handler)
    }

    emit(type, ...args) {
        this.events[type].forEach((item) => {
            Reflect.apply(item, this, args);
        });
    }

    once(type, handler) {
        this.on(type, this._onceWrap(type, handler, this));
    }

    _onceWrap(type, handler, target) {
        const state = { fired: false, handler, type , target};
        const wrapFn = this._onceWrapper.bind(state);
        state.wrapFn = wrapFn;
        return wrapFn;
    }

    _onceWrapper(...args) {
        if (!this.fired) {
            this.fired = true;
            Reflect.apply(this.handler, this.target, args);
            this.target.off(this.type, this.wrapFn);
        }
    }
}

测试代码如下:

const ee = new EventEmitter();// 注册所有事件ee.once('wakeUp', (name) => { console.log(`${name} 1`); });ee.on('eat', (name) => { console.log(`${name} 2`) });ee.on('eat', (name) => { console.log(`${name} 3`) });const meetingFn = (name) => { console.log(`${name} 4`) };ee.on('work', meetingFn);ee.on('work', (name) => { console.log(`${name} 5`) });ee.emit('wakeUp', 'xx');ee.emit('wakeUp', 'xx');         // 第二次没有触发ee.emit('eat', 'xx');ee.emit('work', 'xx');ee.off('work', meetingFn);        // 移除事件ee.emit('work', 'xx');           // 再次工作

参考文献

@lyswhut
Copy link

lyswhut commented Apr 8, 2024

上面的实现无法通过下面的测试用例:

const ee = new EventEmitter();

const aa = (name) => { console.log(`${name} 1`); }
ee.once('wakeUp', aa);
ee.off('wakeUp', aa);
ee.emit('wakeUp', 'xx');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants