EventEmitter 源碼分析與簡易實現

在這裏插入圖片描述


原文出自:https://www.pandashen.com


EventEmitter 簡介

EventEmitter 是 NodeJS 的核心模塊 events 中的類,用於對 NodeJS 中的事件進行統一管理,用 events 特定的 API 對事件進行添加、觸發和移除等等,核心方法的模式類似於發佈訂閱。


實現 EventEmitter

1、EventEmitter 構造函數的實現

// 文件:events.js
function EventEmitter() {
    this._events = Object.create(null);
}

/*
* 其他方法
*/

// 導出自定義模塊
module.export = EventEmitter;

在構造函數 EventEmitter 上有一個屬性 _events,類型爲對象,用於存儲和統一管理所有類型的事件,在創建構造函數的時候導出了 EventEmitter,後面實現其他方法的代碼將放在構造函數與導出中間。

2、事件最大監聽個數

在 EventEmitter 中監聽的每一類事件都有最大監聽個數,超過了這個數值,事件雖然可以正常執行,但是會發出警告信息,其目的是爲了防止內存泄露。

// 默認事件最大監聽個數
EventEmitter.defaultMaxListeners = 10;

這個同類型事件最大個數默認是 10,EventEmitter 當然也有方法設置和獲取這個值,下面是設置和獲取同類型事件最大監聽個數的方法實現。

// 操作最大事件監聽個數
// 設置同類型事件監聽最大個數
EventEmitter.prototype.setMaxListeners = function (count) {
    this._count = count;
}

// 獲取同類型事件監聽最大個數
EventEmitter.prototype.getMaxListeners = function () {
    return this._count || EventEmitter.defaultMaxListeners;
}

在設置這個值的時候其實就是給 EventEmitter 實例添加了一個 _count 的屬性用來存儲設置的新值來作爲這個類型事件的最大監聽個數,在獲取的時候就是獲取 _count,如果沒有設置過就獲取默認值。

3、添加事件監聽

在給 EventEmitter 的實例添加事件監聽時,在 _event 對象中會以事件的類型作爲屬性名,值爲一個數組,每次添加這個類型事件的時候,會將要執行的函數存入這個數組中進行統一管理。

添加事件監聽的方法有 ononceaddListenerprependListenerprependOnceListener

  • on 等同於 addListener 將函數正常添加到 _event 對應事件類型的數組中;
  • once 將函數添加到 _event 對應事件類型的數組中,但是隻能執行一次;
  • prependListener 將函數添加到 _event 對應事件類型的數組中的前面;
  • prependOnceListener 將函數添加到 _event 對應事件類型的數組中的前面,但只能執行一次。

在 EventEmitter 中正常添加事件有四點需要注意:
1、如果其他的類使用 util 模塊的 inherits 方法繼承 EventEmitter 時是無法繼承實例屬性的,在調用操作 _events 的方法中因爲無法獲取到 _events 導致報錯,爲了兼容這種繼承的情況,在獲取不到 _events 時應添加一個 _events 到繼承 EventEmitter 的類的實例上;
2、如果添加事件的類型爲 newListener,傳入要執行的函數會有一個參數 type ,是事件的類型,之後再添加事件的時候,就會執行 newListener 的函數,對添加的事件的事件類型進行處理;
3、on 方法表面上有兩個參數,實際上有第三個參數,爲布爾值,代表是否從 _events 對應事件類型的數組前面追加函數成員;
4、在添加事件的時候需要判斷是否超出這個類型事件的最大監聽個數,如果超出要打印警告信息。

on 方法和 addListener 方法的實現:

// on 和 addListener 方法
// 添加事件監聽
EventEmitter.prototype.on = EventEmitter.prototype.addListener = function (type, callback, flag) {
    // 兼容繼承不存在 _events 的情況
    if (!this._events) this._events = Object.create(null);

    // 如果 type 不是 newListener 就去執行 newListener 的回調
    if (type !== "newListener") {
        // 如果沒添加過 newListener 事件就忽略此處的邏輯
        if (this._events["newListener"] && this._events["newListener"].length) {
            this._events["newListener"].forEach(fn => fn(type));
        }
    }

    // 如果不是第一次添加 callback 存入數組中
    if (this._events[type]) {
        // 是否從數組前面添加 callback
        if (flag) {
            this._events[type].unshift(callback);
        } else {
            this._events[type].push(callback);
        }
    } else {
        // 第一次添加,在 _events 中創建數組並添加 callback 到數組中
        this._events[type] = [callback];
    }

    // 獲取事件最大監聽個數
    let maxListeners = this.getMaxListeners();

    // 判斷 type 類型的事件是否超出最大監聽個數,超出打印警告信息
    if (this._events[type].length - 1 === maxListeners) {
        console.error(`MaxListenersExceededWarning: ${maxListeners + 1} ${type} listeners added`);
    }
}

通過上面代碼可以看出 on 方法的第三個參數其實是服務於 prependListener 方法的,其他添加事件的方法都是基於 on 來實現的,只是在調用 on 的外層做了不同的處理,而我們平時調這些添加事件監聽的方法時都只傳入 typecallback

prependListener 方法的實現:

// prependListener 方法
// 添加事件監聽,從數組的前面追加
EventEmitter.prototype.prependListener = function (type, callback) {
    // 第三個參數爲 true 表示從 _events 對應事件類型的數組前面添加 callback
    this.on(type, callback, true);
}

once 方法的實現:

// once 方法
// 添加事件監聽,只能執行一次
EventEmitter.prototype.once = function (type, callback, flag) {
    let wrap => (...args) {
        callback(...args);

        // 執行 callback 後立即從數組中移除 callback
        this.removeListener(type, wrap);
    }

    // 存儲 callback,確保單獨使用 removeListener 刪除傳入的 callback 時可以被刪除掉
    wrap.realCallback = callback;

    // 調用 on 添加事件監聽
    this.on(type, wrap, flag);
}

想讓事件只執行一次,需要在執行 callback 之後就立即在數組中移除這個函數,由於是同步執行,直接操作 callback 是很難實現的,添加事件其實就是添加 callback_events 對應類型的數組中,我們在使用 once 的時候將 callback 包一層函數名爲 wrap,將這個外層函數存入數組,wrap 的內部邏輯就是真正 callback 的調用和移除 wrap,這裏涉及到事件監聽的移除方法 removeListener 在後面來詳細說明。

once 的第三個參數是爲了 prependOnceListener 服務的,prependOnceListenerprependListener 實現方式類似,不同的是 prependOnceListener 是基於 once 實現的。

prependOnceListener 方法的實現:

// prependOnceListener 方法
// 添加事件監聽,從數組的前面追加,只執行一次
EventEmitter.prototype.prependOnceListener = function (type, callback) {
    // 第三個參數爲 true 表示從 _events 對應事件類型的數組前面添加 callback
    this.once(type, callback, true);
}

4、移除事件監聽

移除事件監聽有兩個方法,分別是 removeListenerremoveAllListeners,前者的作用是移除某個類型數組中的某個回調函數,後者的作用是移除某個類型數組的所有成員,如果類型參數爲空,則清空整個 _events

removeListener 方法的實現:

// removeListener 方法
// 移除事件執行程序
EventEmitter.prototype.removeListener = function (type, callback) {
    if(this._events[type]) {
        // 過濾掉當前傳入的要移除的 callback
        this._events[type] = this._events[type].filter(fn => {
            return fn !== callback && fn !== callback.realCallback;
        });
    }
}

由於 once 中在真正的 callback 包了一層 wrap, 只有在觸發事件時才能執行 wrap 並執行 removeListener 刪掉函數,如果在事件觸發之前使用 removeListener 刪除,傳入的是真正的回調 callback,無法刪除,所以在 once 方法中對真正的 callback 進行了存儲,在 removeListener 中調用 filter 時的返回條件的邏輯中做了處理。

removeAllListeners 方法的實現:

// removeAllListeners 方法
// 移除全部事件執行程序
EventEmitter.prototype.removeAllListeners = function (type) {
    // 存在 type 清空 _events 對應的數組,否則直接清空 _events
    if (type) {
        this._events[type] = [];
    } else {
        this._events = Object.create(null);
    }
}

5、觸發事件監聽

執行事件就比較簡單了,取出 _events 中對應類型的數組進行循環,執行內部的每一個函數,第一個參數爲 type,後面參數會作爲數組中函數執行傳入的參數。

// emit 方法
// 觸發事件
EventEmitter.prototype.emit = function (type, ...args) {
    if (this._events[type]) {
        // 循環執行函數,並將 this 指回 EventEmitter 實例
        this._events[type].forEach(fn => fn.call(this, ...args));
    }
}

6、獲取事件類型名稱集合

// eventNames 方法
// 獲取監聽的所有事件類型
EventEmitter.prototype.eventNames = function () {
    return Object.keys(this._events);
}

7、按事件類型獲取執行程序的集合

// listeners 方法
// 獲取事件類型對應的數組
EventEmitter.prototype.listeners = function (type) {
    return this._events[type];
}


EventEmitter 的基本使用

EventEmitter 的核心邏輯已經實現,由於上面大多數方法需要組合使用,所以在沒有一一驗證,下面讓我們通過一些案例來了解 EventEmitter 的用法。

我們在這裏引入自己自定義的 events 模塊,並使用 util 模塊的 inherits 繼承 EventEmitter,下面是前置代碼,後面將不在重複。

// 文件:events-demo.js
// 引入依賴
const EventEmitter = require("./events");
const util = require("util");

function Girl() {}

// 使 Girl 繼承 EventEmitter
util.inherits(Girl, EventEmitter);

// 創建 Girl 的實例
let girl = new Girl();

案例 1:設置和獲取同類型事件的最大監聽個數

// 文件:events-demo.js
// 獲取事件最大監聽個數
console.log(girl.getMaxListeners()); // 10

// 設置事件最大監聽個數
girl.setMaxListeners(2);
console.log(girl.getMaxListeners()); // 2

案例 2:使用 on 添加事件並執行

// 文件:events-demo.js
girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));

girl.emit("失戀");

// 哭了
// 喝酒

案例 3:使用 prependListener 添加事件並執行

// 文件:events-demo.js
girl.on("失戀", () => console.log("哭了"));
girl.prependListener("失戀", () => console.log("喝酒"));

girl.emit("失戀");

// 喝酒
// 哭了

案例 4:添加 newListener 類型的事件

// 文件:events-demo.js
girl.on("newListener", (type) => console.log(type));

girl.on("失戀", () => console.log("哭了"));
girl.on("和好", () => console.log("開心"));

// 失戀
// 和好

案例 5:添加同類型事件超出最大個數並執行事件

// 文件:events-demo.js
// 設置事件最大監聽個數
girl.setMaxListeners(2);

girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));
girl.on("失戀", () => console.log("吸菸"));

girl.emit("失戀");

// MaxListenersExceededWarning: 3 失戀 listeners added
// 哭了
// 喝酒
// 吸菸

案例 6:對比 on 和 once

// 文件:events-demo.js
girl.on("失戀", () => console.log("哭了"));
girl.once("失戀", () => console.log("喝酒"));

girl.emit("失戀");
girl.emit("失戀");

// 哭了
// 喝酒
// 哭了

案例 7:移除 on 和 once 添加的事件監聽

// 文件:events-demo.js
let cry = () => console.log("哭了");
let drink = () => console.log("喝酒");

girl.on("失戀", cry);
girl.once("失戀", drink);
girl.on("失戀", () => console.log("吸菸"));

girl.removeListener("失戀", cry);
girl.removeListener("失戀", drink);

// 吸菸

案例 8:使用 prependOnceListener 添加事件監聽

// 文件:events-demo.js
girl.on("失戀", () => console.log("哭了"));
girl.prependOnceListener("失戀", () => console.log("喝酒"));

girl.emit("失戀");
girl.emit("失戀");

// 喝酒
// 哭了
// 哭了

案例 9:獲取某個事件類型執行程序的集合

// 文件:events-demo.js
let cry = () => console.log("哭了");
let drink = () => console.log("喝酒");

girl.on("失戀", cry);
girl.once("失戀", drink);
girl.once("失戀", () => console.log("吸菸"));

console.log(girl.listeners("失戀"));

// [ [Function: cry], [Function: drink], [Function] ]

案例 10:獲取所有事件類型名稱

// 文件:events-demo.js
girl.on("失戀", () => console.log("哭了"));
girl.on("和好", () => console.log("開心"));

console.log(girl.eventNames());

// [ '失戀', '和好' ]

案例 11:使用 removeAllListeners 按類型移除事件監聽

// 文件:events-demo.js
girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));
girl.on("和好", () => console.log("開心"));

// 移除 “失戀” 類型事件監聽
girl.removeAllListeners("失戀");

console.log(girl.listeners("失戀"));

// []

案例 12:使用 removeAllListeners 移除全部事件監聽

// 文件:events-demo.js
girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));
girl.on("和好", () => console.log("開心"));

// 移除全部事件監聽
girl.removeAllListeners();

console.log(girl._events);

// {}


EventEmitter 總結

events 模塊在 NodeJS 中的使用率非常高,很多其他模塊的事件執行機制都是通過繼承該模塊的 EventEmitter 類來實現的,比如 ReadStream(可讀流)、WriteStream(可寫流)、net(tcp)和 http 等等,我們也可以通過上面案例的方式創建自己的類去繼承 EventEmitter 來實現事件的管理。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章