问答题11题:说说Node中的EventEmitter? 如何实现一个EventEmitter?

难度:
更新时间:2021-07-25

参考答案:

预览

一、是什么

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

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

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

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

二、使用方法

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

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

基本代码如下所示:

1const EventEmitter = require('events') 2 3class MyEmitter extends EventEmitter {} 4const myEmitter = new MyEmitter() 5 6function callback() { 7 console.log('触发了event事件!') 8} 9myEmitter.on('event', callback) 10myEmitter.emit('event') 11myEmitter.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是一个构造函数,内部存在一个包含所有事件的对象

1class EventEmitter { 2 constructor() { 3 this.events = {}; 4 } 5}

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

1{ 2 "event1": [f1,f2,f3]3 "event2": [f4,f5]4 ... 5}

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

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

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

1on(type, handler) { 2 if (!this.events[type]) { 3 this.events[type] = []; 4 } 5 this.events[type].push(handler); 6} 7 8addListener(type,handler){ 9 this.on(type,handler) 10} 11 12prependListener(type, handler) { 13 if (!this.events[type]) { 14 this.events[type] = []; 15 } 16 this.events[type].unshift(handler); 17}

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

1removeListener(type, handler) { 2 if (!this.events[type]) { 3 return; 4 } 5 this.events[type] = this.events[type].filter(item => item !== handler); 6} 7 8off(type,handler){ 9 this.removeListener(type,handler) 10}

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

1once(type, handler) { 2 this.on(type, this._onceWrap(type, handler, this)); 3 } 4 5 _onceWrap(type, handler, target) { 6 const state = { fired: false, handler, type , target}; 7 const wrapFn = this._onceWrapper.bind(state); 8 state.wrapFn = wrapFn; 9 return wrapFn; 10 } 11 12 _onceWrapper(...args) { 13 if (!this.fired) { 14 this.fired = true; 15 Reflect.apply(this.handler, this.target, args); 16 this.target.off(this.type, this.wrapFn); 17 } 18 }

完整代码如下:

1class EventEmitter { 2 constructor() { 3 this.events = {}; 4 } 5 6 on(type, handler) { 7 if (!this.events[type]) { 8 this.events[type] = []; 9 } 10 this.events[type].push(handler); 11 } 12 13 addListener(type,handler){ 14 this.on(type,handler) 15 } 16 17 prependListener(type, handler) { 18 if (!this.events[type]) { 19 this.events[type] = []; 20 } 21 this.events[type].unshift(handler); 22 } 23 24 removeListener(type, handler) { 25 if (!this.events[type]) { 26 return; 27 } 28 this.events[type] = this.events[type].filter(item => item !== handler); 29 } 30 31 off(type,handler){ 32 this.removeListener(type,handler) 33 } 34 35 emit(type, ...args) { 36 this.events[type].forEach((item) => { 37 Reflect.apply(item, this, args); 38 }); 39 } 40 41 once(type, handler) { 42 this.on(type, this._onceWrap(type, handler, this)); 43 } 44 45 _onceWrap(type, handler, target) { 46 const state = { fired: false, handler, type , target}; 47 const wrapFn = this._onceWrapper.bind(state); 48 state.wrapFn = wrapFn; 49 return wrapFn; 50 } 51 52 _onceWrapper(...args) { 53 if (!this.fired) { 54 this.fired = true; 55 Reflect.apply(this.handler, this.target, args); 56 this.target.off(this.type, this.wrapFn); 57 } 58 } 59}
预览

小程序刷题更方便

预览

关注公众号获取最新面经

预览

加面试交流群

赞赏支持

预览

题库维护不易,您的支持就是我们最大的动力!