EventEmitter 簡介
EventEmitter 是 NodeJS 的核心模組 events
中的類,用於對 NodeJS 中的事件進行統一管理,用 events
特定的 API 對事件進行新增、觸發和移除等等,核心方法的模式類似於釋出訂閱。
實現 EventEmitter
1、EventEmitter 建構函式的實現
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
物件中會以事件的型別作為屬性名,值為一個陣列,每次新增這個型別事件的時候,會將要執行的函式存入這個陣列中進行統一管理。
新增事件監聽的方法有 on
、once
、addListener
、prependListener
和 prependOnceListener
:
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 方法的實現:
// 新增事件監聽
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);
}複製程式碼
4、移除事件監聽
移除事件監聽有兩個方法,分別是 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);
}
}複製程式碼
5、觸發事件監聽
執行事件就比較簡單了,取出 _events
中對應型別的陣列進行迴圈,執行內部的每一個函式,第一個引數為 type
,後面引數會作為陣列中函式執行傳入的引數。
// 觸發事件
EventEmitter.prototype.emit = function (type, ...args) {
if (this._events[type]) {
// 迴圈執行函式,並將 this 指回 EventEmitter 例項
this._events[type].forEach(fn => fn.call(this, ...args));
}
}複製程式碼
6、獲取事件型別名稱集合
// 獲取監聽的所有事件型別
EventEmitter.prototype.eventNames = function () {
return Object.keys(this._events);
}複製程式碼
7、按事件型別獲取執行程式的集合
// 獲取事件型別對應的陣列
EventEmitter.prototype.listeners = function (type) {
return this._events[type];
}複製程式碼
EventEmitter 的基本使用
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);
// {}複製程式碼
EventEmitter 總結
events
模組在 NodeJS 中的使用率非常高,很多其他模組的事件執行機制都是通過繼承該模組的 EventEmitter
類來實現的,比如 ReadStream
(可讀流)、WriteStream
(可寫流)、net
(tcp)和 http
等等,我們也可以通過上面案例的方式建立自己的類去繼承 EventEmitter
來實現事件的管理。