面試官:說說 Node 中的 EventEmitter? 如何實現一個 EventEmitter?

一、是什麼

我們瞭解到,Node採用了事件驅動機制,而EventEmitter就是Node實現事件驅動的基礎

EventEmitter的基礎上,Node幾乎所有的模塊都繼承了這個類,這些模塊擁有了自己的事件,可以綁定/觸發監聽器,實現了異步操作

Node.js 裏面的許多對象都會分發事件,比如 fs.readStream 對象會在文件被打開的時候觸發一個事件

這些產生事件的對象都是 events.EventEmitter 的實例,這些對象有一個 eventEmitter.on() 函數,用於將一個或多個函數綁定到命名事件上

二、使用方法

Nodeevents模塊只提供了一個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用於取消事件的監聽

關於其常見的方法如下:

三、實現過程

通過上面的方法瞭解,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');           // 再次工作

參考文獻

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xiUQ2CfraZB_2ZojLS5uFw