EventEmitter 是 NodeJS 的核心模塊 events
中的類,用於對 NodeJS 中的事件進行統一管理,用 events
特定的 API 對事件進行添加、觸發和移除等等,核心方法的模式相似於發佈訂閱。數組
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
添加事件監聽的方法有 on
、once
、addListener
、prependListener
和 prependOnceListener
:spa
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
// 添加事件監聽
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
的外層作了不一樣的處理,而咱們平時調這些添加事件監聽的方法時都只傳入 type
和 callback
。對象
prependListener 方法的實現:
// 添加事件監聽,從數組的前面追加
EventEmitter.prototype.prependListener = function (type, callback) {
// 第三個參數爲 true 表示從 _events 對應事件類型的數組前面添加 callback
this.on(type, callback, true);
}複製代碼
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
服務的,prependOnceListener
與 prependListener
實現方式相似,不一樣的是 prependOnceListener
是基於 once
實現的。
prependOnceListener 方法的實現:
// 添加事件監聽,從數組的前面追加,只執行一次
EventEmitter.prototype.prependOnceListener = function (type, callback) {
// 第三個參數爲 true 表示從 _events 對應事件類型的數組前面添加 callback
this.once(type, callback, true);
}複製代碼
移除事件監聽有兩個方法,分別是 removeListener
和 removeAllListeners
,前者的做用是移除某個類型數組中的某個回調函數,後者的做用是移除某個類型數組的全部成員,若是類型參數爲空,則清空整個 _events
。
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 方法的實現:
// 移除所有事件執行程序
EventEmitter.prototype.removeAllListeners = function (type) {
// 存在 type 清空 _events 對應的數組,不然直接清空 _events
if (type) {
this._events[type] = [];
} else {
this._events = Object.create(null);
}
}複製代碼
執行事件就比較簡單了,取出 _events
中對應類型的數組進行循環,執行內部的每個函數,第一個參數爲 type
,後面參數會做爲數組中函數執行傳入的參數。
// 觸發事件
EventEmitter.prototype.emit = function (type, ...args) {
if (this._events[type]) {
// 循環執行函數,並將 this 指回 EventEmitter 實例
this._events[type].forEach(fn => fn.call(this, ...args));
}
}複製代碼
// 獲取監聽的全部事件類型
EventEmitter.prototype.eventNames = function () {
return Object.keys(this._events);
}複製代碼
// 獲取事件類型對應的數組
EventEmitter.prototype.listeners = function (type) {
return this._events[type];
}複製代碼
EventEmitter 的核心邏輯已經實現,因爲上面大多數方法須要組合使用,因此在沒有一一驗證,下面讓咱們經過一些案例來了解 EventEmitter 的用法。
咱們在這裏引入本身自定義的 events
模塊,並使用 util
模塊的 inherits
繼承 EventEmitter,下面是前置代碼,後面將不在重複。
// 引入依賴
const EventEmitter = require("./events");
const util = require("util");
function Girl() {}
// 使 Girl 繼承 EventEmitter
util.inherits(Girl, EventEmitter);
// 建立 Girl 的實例
let girl = new Girl();複製代碼
案例 1:設置和獲取同類型事件的最大監聽個數
// 獲取事件最大監聽個數
console.log(girl.getMaxListeners()); // 10
// 設置事件最大監聽個數
girl.setMaxListeners(2);
console.log(girl.getMaxListeners()); // 2複製代碼
案例 2:使用 on 添加事件並執行
girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));
girl.emit("失戀");
// 哭了
// 喝酒複製代碼
案例 3:使用 prependListener 添加事件並執行
girl.on("失戀", () => console.log("哭了"));
girl.prependListener("失戀", () => console.log("喝酒"));
girl.emit("失戀");
// 喝酒
// 哭了複製代碼
案例 4:添加 newListener 類型的事件
girl.on("newListener", (type) => console.log(type));
girl.on("失戀", () => console.log("哭了"));
girl.on("和好", () => console.log("開心"));
// 失戀
// 和好複製代碼
案例 5:添加同類型事件超出最大個數並執行事件
// 設置事件最大監聽個數
girl.setMaxListeners(2);
girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));
girl.on("失戀", () => console.log("吸菸"));
girl.emit("失戀");
// MaxListenersExceededWarning: 3 失戀 listeners added
// 哭了
// 喝酒
// 吸菸複製代碼
案例 6:對比 on 和 once
girl.on("失戀", () => console.log("哭了"));
girl.once("失戀", () => console.log("喝酒"));
girl.emit("失戀");
girl.emit("失戀");
// 哭了
// 喝酒
// 哭了複製代碼
案例 7:移除 on 和 once 添加的事件監聽
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 添加事件監聽
girl.on("失戀", () => console.log("哭了"));
girl.prependOnceListener("失戀", () => console.log("喝酒"));
girl.emit("失戀");
girl.emit("失戀");
// 喝酒
// 哭了
// 哭了複製代碼
案例 9:獲取某個事件類型執行程序的集合
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:獲取全部事件類型名稱
girl.on("失戀", () => console.log("哭了"));
girl.on("和好", () => console.log("開心"));
console.log(girl.eventNames());
// [ '失戀', '和好' ]複製代碼
案例 11:使用 removeAllListeners 按類型移除事件監聽
girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));
girl.on("和好", () => console.log("開心"));
// 移除 「失戀」 類型事件監聽
girl.removeAllListeners("失戀");
console.log(girl.listeners("失戀"));
// []複製代碼
案例 12:使用 removeAllListeners 移除所有事件監聽
girl.on("失戀", () => console.log("哭了"));
girl.on("失戀", () => console.log("喝酒"));
girl.on("和好", () => console.log("開心"));
// 移除所有事件監聽
girl.removeAllListeners();
console.log(girl._events);
// {}複製代碼
events
模塊在 NodeJS 中的使用率很是高,不少其餘模塊的事件執行機制都是經過繼承該模塊的 EventEmitter
類來實現的,好比 ReadStream
(可讀流)、WriteStream
(可寫流)、net
(tcp)和 http
等等,咱們也能夠經過上面案例的方式建立本身的類去繼承 EventEmitter
來實現事件的管理。