面試題之如何模擬實現node中的Events模塊(通俗易懂版)

Nodejs 的大部分核心 API 都是基於異步事件驅動設計的,事件驅動核心是經過 node 中 Events 對象來實現事件的發送和監聽回調綁定,咱們經常使用的 stream 模塊也是依賴於 Events 模塊是來實現數據流之間的回調通知,如在數據到來時觸發 data 事件,流對象爲可讀狀態觸發 readable 事件,當數據讀寫完畢後發送 end 事件。node

既然 Events 模塊如此重要,咱們有必要來學習一下 Events 模塊的基本使用,以及如何模擬實現 Events 模塊中經常使用的 APIapi

1、Events 模塊的基本使用以及簡單實現

首先咱們瞭解一下 Events 模塊的基本用法,其實 Events 模塊本質上是觀察者模式的實現,所謂觀察者模式就是:數組

它定義了對象間的一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,全部依賴於它的對象都將獲得通知bash

觀察者模式有對應的觀察者以及被觀察的對象,在 Events 模塊中,對應的實現就是 on 和 emit 函數app

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('嗨', (str) => {
  console.log(str);
});
myEmitter.emit('嗨','你好');
複製代碼

從上述的使用中,咱們能夠知道 on 是用來監聽事件的發生,而 emit 是用來觸發事件的發生,一旦 emit 觸發了事件,on 就會被通知到,從而執行對應的回調函數。異步

有了這個實例,咱們能夠思考下如何實現這個 EventEmitter 類。函數

思路:當咱們執行 on 函數時,咱們能夠將回調函數保存起來,等到 emit 觸發了事件時,將回調函數拿出來執行,那麼就能夠實現了事件的監聽以及訂閱了。學習

class EventEmitter{
   constructor(){
      #事件監聽函數保存的地方
      this.events={};
   }
   on(eventName,listener){
        if (this.events[eventName]) {
            this.events[eventName].push(listener);
        } else {
            #若是沒有保存過,將回調函數保存爲數組
            this.events[eventName] = [listener];
        }
   }
   emit(eventName){
      #emit觸發事件,把回調函數拉出來執行
      this.events[eventName] && this.events[eventName].forEach(listener => listener())
   }
}
複製代碼

上述就實現了一個簡單的 EventEmitter 類,下面來實例一下:ui

let event = new EventEmitter();
event.on('嗨',function(){
   console.log('你好');
});
event.emit('嗨');
#輸出:你好
複製代碼

完善:咱們注意到在原生的 EventEmitter 類中,emit 是能夠傳遞參數到咱們的回調函數中,那麼咱們實現的類也應該支持傳遞參數。咱們對 emit 進行以下更改this

emit(eventName,...rest){
  #emit觸發事件,把回調函數拉出來執行
  this.events[eventName] && this.events[eventName].forEach(listener => listener.apply(this,rest))
}
複製代碼

完善以後,從新實例化,以下:

let event = new EventEmitter();
event.on('嗨',function(str){
   console.log(str);
});
event.emit('嗨','你好');
#輸出:你好
複製代碼

2、Events 模塊中經常使用的 api

Events 模塊中除了 on、emit 函數以外,還包含了不少經常使用的 api,咱們一一來介紹幾個實用的 api

API名稱 API方法描述
addListener(eventName, listener) on(eventName, listener)別名,爲指定事件添加一個監聽器到監聽器數組的尾部
removeListener(eventName, listener) 從名爲 eventName 的事件的監聽器數組中移除指定的 listener
removeAllListeners(eventName, listener) 移除所有監聽器或指定的 eventName 事件的監聽器
once(eventName, listener) 添加單次監聽器 listener 到名爲 eventName 的事件
listeners(eventName) 返回名爲 eventName 的事件的監聽器數組的副本
setMaxListeners(n) 能夠爲指定的 EventEmitter 實例修改監聽器數量限制

1. addListener 與 on 方法使用與實現

在 Events 模塊中,addListener 與 on 方法的使用是完成相同的,只是名字不一樣,咱們能夠經過原型來給兩個函數創建相等關聯

EventEmitter.prototype.addListener=EventEmitter.prototype.on
複製代碼

2. removeListener 與 off 方法使用與實現

removeListener 方法能夠從指定名字的監聽器數組中移除指定的 listener,這樣的話,當再次 emit 事件的時候,不會觸發 on 綁定的回調函數,以下:

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
  console.log(str);
}
myEmitter.on('嗨', callback);
myEmitter.emit('嗨','你好');#輸出:你好
myEmitter.removeListener('嗨',callback);
myEmitter.emit('嗨','你好');#無輸出
複製代碼

實現思路:咱們只要在執行 removeListener 函數的時候,將先前保存的回調函數去除掉便可

removeListener (eventName,listener) {
    #保證回調函數數組存在,同時去除指定的listener
    this.events[eventName] && this.events[eventName] = this.events[eventName].filter(l => l != listener);
}
複製代碼

同時 removeListener 與 off 方法也是功能徹底相同,只是命名不一樣,所以能夠經過以下方法賦值:

EventEmitter.prototype.removeListener=EventEmitter.prototype.off
複製代碼

3. removeAllListeners 方法使用與實現

removeAllListeners 移除所有監聽器或指定的 eventName 事件的監聽器,其實 removeAllListeners 就包含了 removeListener 的功能,只是 removeListener 只能指定特定的監聽器,removeAllListeners 能夠移除所有監聽器。 實現思路:在執行 removeAllListeners,將全部的回調函數都給去除便可

removeAllListeners (eventName) {
    #移除所有監聽器
    delete this.events[eventName]
}
複製代碼

4. once 方法使用與實現

once 方法的描述是添加單次監聽器 listener 到名爲 eventName 的事件,其實就是經過 once 添加的監聽器,只能執行一次,執行一次以後就會被銷燬,不能再次執行

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.once('嗨', (str) => {
  console.log(str);
});
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好'); #只能輸出一次你好
複製代碼

實現思路:當 once 監聽的事件回調函數執行以後,經過 removeListener 將事件監聽器給解綁掉,那麼事件再次被 emit 的時候,就不會再次執行回調,這樣就能保證事件回調只能執行一次

once (eventName, listener) {
    #從新改變監聽回調函數,使其執行以後能夠被銷燬
    let reListener = (...rest) => {
        listener.apply(this,rest);
        #執行完以後解除事件綁定
        this.removeListener(type,wrapper);
    }
    this.on(eventName,reListener);
}
複製代碼

5. listeners 方法使用與實現

listeners 方法返回名爲 eventName 的事件的監聽器數組的副本,其實就是獲取 eventName 中全部的回調函數,這個實現起來很容易,就很少贅述了,代碼以下:

listeners (eventName) {
    return this.events[eventName]
}
複製代碼

6. setMaxListeners 方法使用與實現

默認狀況下,若是爲特定事件添加了超過 10 個監聽器,則 EventEmitter 會打印一個警告。 這有助於發現內存泄露, 可是,並非全部的事件都要限制 10 個監聽器。 emitter.setMaxListeners() 方法能夠爲指定的 EventEmitter 實例修改限制。 值設爲 Infinity(或 0)表示不限制監聽器的數量。

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
    console.log(str);
}
for (let i = 0; i <= 11; i++) {
    myEmitter.on('嗨', callback);
}
myEmitter.emit('嗨', '你好');
複製代碼

輸入結果如圖:

實現思路:

  1. 咱們先將特定事件的監聽器最大設置爲常量10
constructor(){
  #事件監聽函數保存的地方
  this.events={};
  #最大監聽器數量
  this._maxListeners = 10;
}
複製代碼
  1. 而後在咱們的 on 函數中,對這個監聽器的數量進行判斷,從而做出提示
on(eventName,listener){
    if (this.events[eventName]) {
        this.events[eventName].push(listener);
        #若是超過最大限度,以及不爲0,則做出內存泄漏提示
        if (this._maxListeners != 0 && this.events[type].length >= this._maxListeners) {
            console.error('超過最大的監聽數量可能會致使內存泄漏');
        }
    } else {
        #若是沒有保存過,將回調函數保存爲數組
        this.events[eventName] = [listener];
    }
}
複製代碼
  1. 咱們也支持對 _maxListeners 變量根據用戶的輸入進行更改,即咱們的 setMaxListeners() 函數
setMaxListeners(MaxListeners) {
    this._maxListeners = MaxListeners
}
複製代碼

3、總結

本文從 node 的 Events 模塊出發,而後去介紹了 Events 模塊經常使用 API 的使用,從中經過一步一步簡易去思考這些 API 使用的內部原理,簡易的實現了這些 API,但願你們看完文章以後,能對 Events 模塊有進一步的理解。

相關文章
相關標籤/搜索