面試官:說說Node中的EventEmitter? 如何實(shí)現(xiàn)一個(gè)EventEmitter?
本文轉(zhuǎn)載自微信公眾號「JS每日一題」,作者灰灰。轉(zhuǎn)載本文請聯(lián)系JS每日一題公眾號。
一、是什么
我們了解到,Node采用了事件驅(qū)動機(jī)制,而EventEmitter就是Node實(shí)現(xiàn)事件驅(qū)動的基礎(chǔ)
在EventEmitter的基礎(chǔ)上,Node幾乎所有的模塊都繼承了這個(gè)類,這些模塊擁有了自己的事件,可以綁定/觸發(fā)監(jiān)聽器,實(shí)現(xiàn)了異步操作
Node.js 里面的許多對象都會分發(fā)事件,比如 fs.readStream 對象會在文件被打開的時(shí)候觸發(fā)一個(gè)事件
這些產(chǎn)生事件的對象都是 events.EventEmitter 的實(shí)例,這些對象有一個(gè) eventEmitter.on() 函數(shù),用于將一個(gè)或多個(gè)函數(shù)綁定到命名事件上
二、使用方法
Node的events模塊只提供了一個(gè)EventEmitter類,這個(gè)類實(shí)現(xiàn)了Node異步事件驅(qū)動架構(gòu)的基本模式——觀察者模式
在這種模式中,被觀察者(主體)維護(hù)著一組其他對象派來(注冊)的觀察者,有新的對象對主體感興趣就注冊觀察者,不感興趣就取消訂閱,主體有更新的話就依次通知觀察者們
基本代碼如下所示:
- const EventEmitter = require('events')
 - class MyEmitter extends EventEmitter {}
 - const myEmitter = new MyEmitter()
 - function callback() {
 - console.log('觸發(fā)了event事件!')
 - }
 - myEmitter.on('event', callback)
 - myEmitter.emit('event')
 - myEmitter.removeListener('event', callback);
 
通過實(shí)例對象的on方法注冊一個(gè)名為event的事件,通過emit方法觸發(fā)該事件,而removeListener用于取消事件的監(jiān)聽
關(guān)于其常見的方法如下:
- emitter.addListener/on(eventName, listener) :添加類型為 eventName 的監(jiān)聽事件到事件數(shù)組尾部
 - emitter.prependListener(eventName, listener):添加類型為 eventName 的監(jiān)聽事件到事件數(shù)組頭部
 - emitter.emit(eventName[, ...args]):觸發(fā)類型為 eventName 的監(jiān)聽事件
 - emitter.removeListener/off(eventName, listener):移除類型為 eventName 的監(jiān)聽事件
 - emitter.once(eventName, listener):添加類型為 eventName 的監(jiān)聽事件,以后只能執(zhí)行一次并刪除
 - emitter.removeAllListeners([eventName]):移除全部類型為 eventName 的監(jiān)聽事件
 
三、實(shí)現(xiàn)過程
通過上面的方法了解,EventEmitter是一個(gè)構(gòu)造函數(shù),內(nèi)部存在一個(gè)包含所有事件的對象
- class EventEmitter {
 - constructor() {
 - this.events = {};
 - }
 - }
 
其中events存放的監(jiān)聽事件的函數(shù)的結(jié)構(gòu)如下:
- {
 - "event1": [f1,f2,f3],
 - "event2": [f4,f5],
 - ...
 - }
 
然后開始一步步實(shí)現(xiàn)實(shí)例方法,首先是emit,第一個(gè)參數(shù)為事件的類型,第二個(gè)參數(shù)開始為觸發(fā)事件函數(shù)的參數(shù),實(shí)現(xiàn)如下:
- emit(type, ...args) {
 - this.events[type].forEach((item) => {
 - Reflect.apply(item, this, args);
 - });
 - }
 
當(dāng)實(shí)現(xiàn)了emit方法之后,然后實(shí)現(xiàn)on、addListener、prependListener這三個(gè)實(shí)例方法,都是添加事件監(jiān)聽觸發(fā)函數(shù),實(shí)現(xiàn)也是大同小異
- 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);
 - }
 
緊接著就是實(shí)現(xiàn)事件監(jiān)聽的方法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)
 - }
 
最后再來實(shí)現(xiàn)once方法, 再傳入事件監(jiān)聽處理函數(shù)的時(shí)候進(jìn)行封裝,利用閉包的特性維護(hù)當(dāng)前狀態(tài),通過fired屬性值判斷事件函數(shù)是否執(zhí)行過
- 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'); // 第二次沒有觸發(fā)
 - ee.emit('eat', 'xx');
 - ee.emit('work', 'xx');
 - ee.off('work', meetingFn); // 移除事件
 - ee.emit('work', 'xx'); // 再次工作
 
參考文獻(xiàn)
- http://nodejs.cn/api/events.html#events_class_eventemitter
 - https://segmentfault.com/a/1190000015762318
 - https://juejin.cn/post/6844903781230968845
 - https://vue3js.cn/interview
 
















 
 
 
















 
 
 
 