您好,登錄后才能下訂單哦!
本篇內容主要講解“Node.js中的EventEmitter模塊怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Node.js中的EventEmitter模塊怎么使用”吧!
EventEmitter 為我們提供了事件訂閱機制,通過引入 events
模塊來使用它。
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); // 監聽 data 事件 eventEmitter.on("data", () => { console.log("data"); }); // 觸發 data 事件 eventEmitter.emit("data");
上述代碼我們使用 on
方法來為事件綁定回調函數,使用 emit
方法來觸發一個事件。
我們可以通過 on
和 addListener
方法來為某事件添加一個監聽器,二者的使用是一樣
eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); });
第一個參數為事件名,第二個參數為對應的回調函數,當 EventEmitter 實例對象調用 emit
觸發相應的事件時便會調用該回調函數,如
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); }); eventEmitter.emit("data");
在控制臺會打印出兩次 data
data data
從上面的例子也可以看出,可以為同一事件綁定多個回調函數。
當使用 on
或 addListener
綁定多個回調函數時,觸發的順序就是添加的順序,如
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data 1"); }); eventEmitter.on("data", () => { console.log("data 2"); }); eventEmitter.on("data", () => { console.log("data 3"); }); eventEmitter.emit("data");
會在控制臺依次打印出
data 1 data 2 data 3
并且使用 on
方法綁定事件時,并不會做去重檢查
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); const listener = () => { console.log("lsitener"); } eventEmitter.on("data", listener); eventEmitter.on("data", listener); eventEmitter.emit("data");
控制臺的打印結果為
lsitener lsitener
上面的程序為事件綁定了兩次 listener
這個函數,但是內部并不會檢查是否已經添加過這個回調函數,然后去重,所以上面在控制臺打印出了兩次 listener。
另外回調函數還可以接收參數,參數通過 emit
觸發事件時傳入,如
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", data => { console.log(data); }); // 為回調函數傳入參數 HelloWorld! eventEmitter.emit("data", "HelloWorld!");
上面我們使用 emit
觸發事件時,還傳遞了額外的參數,這個參數會被傳遞給回調函數。
另外一個比較關心的問題,事件的觸發是同步的還是異步的,我們做一個實驗
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("觸發了 data 事件!"); }); console.log("start"); eventEmitter.emit("data"); console.log("end");
上面我們我們在觸發事件前后都向控制臺打印了信息,如果觸發事件后是異步執行的,那么后面的打印語句就會先執行,否則如果是同步的話,就會先執行事件綁定的回調函數。執行結果如下
start 觸發了 data 事件! end
可見事件觸發是同步執行的。
off
與 removeListener
方法的作用同 on
和 addLsitener
的作用是相反的,它們的作用是為某個事件刪除對應的回調函數
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); let listener1 = () => { console.log("listener1"); } let listener2 = () => { console.log("listener2"); } eventEmitter.on("data", listener1); eventEmitter.on("data", listener2); // 第一次觸發,兩個回調函數否會執行 eventEmitter.emit("data"); eventEmitter.off("data", listener1); // 第二次觸發,只會執行 listener2 eventEmitter.emit("data");
控制臺打印結果為
listener1 listener2 listener2
第一次觸發事件時,兩個事件都會觸發,然后我們為事件刪除了 listener1 這個回調函數,所以第二次觸發時,只會觸發 listener2。
注意:如果我們使用
on
或者addListener
綁定的是一個匿名函數,那么便無法通過off
和removeListener
去解綁一個回調函數,因為它會通過比較兩個函數的引用是否相同來解綁函數的。
使用 once
可以綁定一個只執行一次的回調函數,當觸發一次之后,該回調函數便自動會被解綁
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("data"); }); eventEmitter.emit("data"); eventEmitter.emit("data");
上述代碼我們使用 once
為 data
事件綁定了一個回調函數,然后使用 emit
方法觸發了兩次,因為使用 once
綁定的回調函數只會被觸發一次,所以第二次觸發,回調函數不會執行,所以在控制臺只打印了一次 data。
另外同 on
綁定的回調函數一樣,我們同樣可以通過 emit
方法向回調函數傳遞參數
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", data => { console.log(data); }); eventEmitter.emit("data", "Hello");
控制臺打印結果
Hello
使用 on
或者 addListener
為事件綁定的回調函數會被根據添加的順序執行,而使用 prependLsitener
綁定的事件回調函數會在其他回調函數之前執行
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("on"); }); eventEmitter.prependListener("data", () => { console.log("prepend"); }); eventEmitter.emit("data");
上述代打我們先用控制臺的打印結果為
prepend on
prependOnceListener
同 prependListener
,不過它綁定的回調函數只會被執行一次
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("on"); }); eventEmitter.prependOnceListener("data", () => { console.log("prepend once"); }); eventEmitter.emit("data"); eventEmitter.emit("data");
上面我們使用 prependOnceListener
綁定了一個回調函數,當觸發事件時,該回調函數會在其他函數之前執行,并且只會執行一次,所以當第二次我們觸發函數時,該回調函數不會執行,控制臺打印結果為
prepend once on on
removeAllListeners([event])
方法可以刪除事件 event
綁定的所有回調函數,如果沒有傳入 event
參數的話,那么該方法就會刪除所有事件綁定的回調函數
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data 1"); }); eventEmitter.on("data", () => { console.log("data 2"); }); eventEmitter.emit("data"); eventEmitter.removeAllListeners("data"); eventEmitter.emit("data");
上面程序為 data
事件綁定了兩個回調函數,并且在調用 removeAllListeners
方法之前分別觸發了一次 data
事件,第二次觸發 data
事件時,不會有任何的回調函數被執行,removeAllListeners
刪除了 data
事件綁定的所有回調函數。控制臺的打印結果為:
data 1 data 2
通過 eventNames
方法我們可以知道為哪些事件綁定了回調函數,它返回一個數組
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("start", () => { console.log("start"); }); eventEmitter.on("end", () => { console.log("end"); }); eventEmitter.on("error", () => { console.log("error"); }); console.log(eventEmitter.eventNames()); // [ 'start', 'end', 'error' ]
如果我們將某事件的所有回調函數刪除后,此時 eventNames
便不會返回該事件了
eventEmitter.removeAllListeners("error"); console.log(eventEmitter.eventNames()); // [ 'start', 'end' ]
listenerCount
方法可以得到某個事件綁定了多少個回調函數
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { }); eventEmitter.on("data", () => { }); console.log(eventEmitter.listenerCount("data")); // 2
setMaxListeners
是用來設置最多為每個事件綁定多少個回調函數,但是實際上是可以綁定超過設置的數目的回調函數的,不過當你綁定超過指定數目的回調函數時,會在控制臺給出一個警告
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); // 設置只能為每個回調函數綁定 1 個回調函數 eventEmitter.setMaxListeners(1); // 為 data 事件綁定了三個回調函數 eventEmitter.on("data", () => { console.log("data 1"); }); eventEmitter.on("data", () => { console.log("data 2"); }); eventEmitter.on("data", () => { console.log("data 3"); });
運行上述程序,控制臺打印結果為
data 1 data 2 data 3 (node:36928) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 2 data listeners added to [EventEmitter]. Use emitter.setMaxListeners() to increase limit
可見事件綁定的三個回調函數都可以被觸發,并且在控制臺打印出了一條警告信息。
getMaxListeners
是獲得能為每個事件綁定多少個回調函數的方法,使用 setMaxListeners
設置的值時多少,返回的值就是多少
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.setMaxListeners(1); console.log(eventEmitter.getMaxListeners()); // 1
如果沒有使用 setMaxLsiteners
進行設置,那么默認能夠為每個事件最多綁定 10
個回調函數,可以通過 EventEmitter
的 defaultMaxListeners
屬性獲得該值
const {EventEmitter} = require("events"); console.log(EventEmitter.defaultMaxListeners); // 10
當我們使用 once
綁定一個回調函數時,不會直接為該事件綁定該函數,而是會使用一個函數包裝該函數,這個包裝函數稱為 wrapper
,然后為該事件綁定 wrapper
函數,在 wrapper
函數內部,設定了當執行一次之后將自己解綁的邏輯。
listeners
返回指定事件綁定的回調函數組成的數組,而 rawListeners
也是返回指定事件綁定的回調函數組成的數組,與 listeners
不同的是,對于 once
綁定的回調函數返回的是 wrapper
,而不是原生綁定的函數。
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("once"); }) let fns = eventEmitter.listeners("data"); // once 綁定的函數,不是 wrapper,內部沒有解綁的邏輯,所以后面觸發 data 事件時還會執行 once 綁定的函數 fns[0]() eventEmitter.emit("data");
控制臺打印結果為
once once
下面將上面的 listeners
替換為 rawListeners
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("once"); }) let fns = eventEmitter.rawListeners("data"); // 因為返回的是 once 綁定函數的 wrapper,其內部有執行一次后解綁的邏輯 // 所以后面觸發事件時 once 綁定的函數不會再執行 fns[0]() eventEmitter.emit("data");
控制臺的打印結果為
once
在這個小節將從零實現一個 EventEmitter,來加深對該模塊的理解。首先我們需要準備一個 listeners
來存儲所有綁定的回調函數,它是一個 Map
對象,鍵是事件名,而值是一個數組,數組中保存的是該事件綁定的回調函數。
class EventEmitter { constructor() { this.listeners = new Map(); } }
使用 on
綁定回調函數時,我們先判斷 Map
集合中是否有為該事件綁定回調函數,如果有取出對應數組,并添加該回調函數進數組,沒有則新建一個數組,添加該回調函數,并添加進 Map
集合
on(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.push(callback); }
addListener
的功能與 on
是一樣的,我們直接調用 on
方法即可
addListener(event, callback) { this.on(event, callback); }
當我們使用 emit
觸發事件時,我們從 Map
取出對應的回調函數組成的數組,然后依次取出函數執行。另外我們還可以通過 emit
傳遞參數
emit(event, ...args) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); let values = []; for(let fn of fns) { values.push(fn); } for (let fn of values) { fn(...args); } }
這里你可能會覺得我寫的有點復雜,所以你會覺得直接這么寫更好
emit(event, ...args) { if(!this.listeners.has(event)) { return; } for (let fn of fns) { fn(...args); } }一開始我也是這么寫的,但是因為
once
綁定的函數它在執行完畢后將自己從數組中移除,并且是同步的,所以在執行循環的時候,數組是在不斷變化的,使用上述的方式會使得一些回調函數會被漏掉,所以我才會先將數組中的函數復制到另一個數組,然后遍歷這個新的數組,因為once
綁定的函數它只會刪除原數組中的函數,而不會刪除新的這個數組,所以新數組的長度在遍歷的過程不會改變,也就不會發生漏掉函數未執行的情況。
實現 prependListener
的邏輯同 on
一樣,不過我們是往數組的最前方添加回調函數
prependListener(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.unshift(callback); }
使用 off
方法是用來解綁事件的,在數組中找到指定的函數,然后刪除即可
off(event, callback) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); // 找出數組中的回調函數,然后刪除 for (let i = 0; i < fns.length; i++) { if(fns[i] === callback) { fns.splice(i, 1); break; } } // 如果刪除回調函數后,數組為空,則刪除該事件 if (fns.length === 0) { this.listeners.delete(event); } }
removeListener
同 off
的作用一樣,我們在內部直接調用 off
方法即可
removeListener(event, callback) { this.off(event, callback); }
使用 once
綁定一個只執行一次的函數,所以我們需要將綁定的回調函數使用一個函數包裝一下,然后添加進數組中,這個包裝函數我們稱之為 wrapper
。在包裝函數中,當執行一遍后會將自己從數組中刪除
once(event, callback) { let wrapper = (...args) => { callback(...args); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.push(wrapper); }
prependOnceListener
的實現同 once
,只是向數組的開頭插入函數,將上面代碼中的 push
換為 unshift
即可
prependOnceListener(event, callback) { let wrapper = (...args) => { callback(...args); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.unshift(wrapper); }
直接從刪除對應的事件,如果沒有傳入具體事件的話,則需要刪除所有的事件
removeAllListeners(event) { // 如果沒有傳入 event,則刪除所有事件 if (event === undefined) { this.listeners = new Map(); return; } this.listeners.delete(event); }
獲得已經綁定了哪些事件
eventNames() { return [...this.listeners.keys()]; }
獲得某事件綁定可多少個回調函數
listenerCount(event) { return this.listeners.get(event).length; }
上述的實現有一個 bug,那就是無法刪除使用
once
綁定的函數,我的想法是使用一個Map
將once
綁定的函數同對應的wrapper
對應,刪除時即可根據once
的回調函數找到對應的wrapper
然后刪除constructor() { this.listeners = new Map(); // 保存 once 的回調函數與對應的 wrapper this.onceToWrapper = new Map(); } once(event, callback) { let wrapper = (...args) => { callback(...args); // 刪除之前,刪除 callback 和 wrapper 的關系 this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); // 添加之前,綁定 callback 和 wrapper 的關系 this.onceToWrapper.set(callback, wrapper); fns.push(wrapper); } prependOnceListener(event, callback) { let wrapper = (...args) => { callback(...args); // 同上 this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); // 同上 this.onceToWrapper.set(callback, wrapper); fns.unshift(wrapper); } off(event, callback) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); // 先從 onceToWrapper 中查找是否有對應的 wrapper,如果有說明是 once 綁定的 callback = this.onceToWrapper.get(callback) || callback; for (let i = 0; i < fns.length; i++) { if(fns[i] === callback) { fns.splice(i, 1); break; } } if (fns.length === 0) { this.listeners.delete(event); } }
全部代碼如下
class EventEmitter { constructor() { this.listeners = new Map(); this.onceToWrapper = new Map(); } on(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.push(callback); } addListener(event, callback) { this.on(event, callback); } emit(event, ...args) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); let values = []; for(let fn of fns) { values.push(fn); } for (let fn of values) { fn(...args); } } prependListener(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.unshift(callback); } off(event, callback) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); callback = this.onceToWrapper.get(callback) || callback; for (let i = 0; i < fns.length; i++) { if(fns[i] === callback) { fns.splice(i, 1); break; } } if (fns.length === 0) { this.listeners.delete(event); } } removeListener(event, callback) { this.off(event, callback); } once(event, callback) { let wrapper = (...args) => { callback(...args); this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); this.onceToWrapper.set(callback, wrapper); fns.push(wrapper); } prependOnceListener(event, callback) { let wrapper = (...args) => { callback(...args); this.onceToWrapper.delete(callback); this.off(event, wrapper); } if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); this.onceToWrapper.set(callback, wrapper); fns.unshift(wrapper); } removeAllListeners(event) { if (event === undefined) { this.listeners = new Map(); return; } this.listeners.delete(event); } eventNames() { return [...this.listeners.keys()]; } listenerCount(event) { return this.listeners.get(event).length; } }
到此,相信大家對“Node.js中的EventEmitter模塊怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。