NodeJS中的事件(EventEmitter) API詳解(附源碼)

EventEmitter 簡介

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

實現 EventEmitter

一、EventEmitter 構造函數的實現

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

/*
* 其餘方法
*/

// 導出自定義模塊
module.export = EventEmitter;複製代碼

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

二、事件最大監聽個數

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

默認事件最大監聽個數
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,若是沒有設置過就獲取默認值。ui

三、添加事件監聽

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

添加事件監聽的方法有 ononceaddListenerprependListenerprependOnceListenerspa

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

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

on 方法和 addListener 方法的實現:code

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);
}複製代碼

四、移除事件監聽

移除事件監聽有兩個方法,分別是 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);
    }
}複製代碼

五、觸發事件監聽

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

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

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

eventNames 方法
// 獲取監聽的全部事件類型
EventEmitter.prototype.eventNames = function () {
    return Object.keys(this._events);
}複製代碼

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

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 來實現事件的管理。


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

相關文章
相關標籤/搜索