本篇內(nèi)容主要講解“Node.js中的EventEmitter模塊怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Node.js中的EventEmitter模塊怎么使用”吧!
在沙依巴克等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作按需設(shè)計(jì)網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),全網(wǎng)營銷推廣,外貿(mào)網(wǎng)站建設(shè),沙依巴克網(wǎng)站建設(shè)費(fèi)用合理。
EventEmitter 為我們提供了事件訂閱機(jī)制,通過引入 events
模塊來使用它。
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); // 監(jiān)聽 data 事件 eventEmitter.on("data", () => { console.log("data"); }); // 觸發(fā) data 事件 eventEmitter.emit("data");
上述代碼我們使用 on
方法來為事件綁定回調(diào)函數(shù),使用 emit
方法來觸發(fā)一個(gè)事件。
我們可以通過 on
和 addListener
方法來為某事件添加一個(gè)監(jiān)聽器,二者的使用是一樣
eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); });
第一個(gè)參數(shù)為事件名,第二個(gè)參數(shù)為對(duì)應(yīng)的回調(diào)函數(shù),當(dāng) EventEmitter 實(shí)例對(duì)象調(diào)用 emit
觸發(fā)相應(yīng)的事件時(shí)便會(huì)調(diào)用該回調(diào)函數(shù),如
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("data"); }); eventEmitter.addListener("data", () => { console.log("data"); }); eventEmitter.emit("data");
在控制臺(tái)會(huì)打印出兩次 data
data data
從上面的例子也可以看出,可以為同一事件綁定多個(gè)回調(diào)函數(shù)。
當(dāng)使用 on
或 addListener
綁定多個(gè)回調(diào)函數(shù)時(shí),觸發(fā)的順序就是添加的順序,如
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");
會(huì)在控制臺(tái)依次打印出
data 1 data 2 data 3
并且使用 on
方法綁定事件時(shí),并不會(huì)做去重檢查
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); const listener = () => { console.log("lsitener"); } eventEmitter.on("data", listener); eventEmitter.on("data", listener); eventEmitter.emit("data");
控制臺(tái)的打印結(jié)果為
lsitener lsitener
上面的程序?yàn)槭录壎藘纱?listener
這個(gè)函數(shù),但是內(nèi)部并不會(huì)檢查是否已經(jīng)添加過這個(gè)回調(diào)函數(shù),然后去重,所以上面在控制臺(tái)打印出了兩次 listener。
另外回調(diào)函數(shù)還可以接收參數(shù),參數(shù)通過 emit
觸發(fā)事件時(shí)傳入,如
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", data => { console.log(data); }); // 為回調(diào)函數(shù)傳入?yún)?shù) HelloWorld! eventEmitter.emit("data", "HelloWorld!");
上面我們使用 emit
觸發(fā)事件時(shí),還傳遞了額外的參數(shù),這個(gè)參數(shù)會(huì)被傳遞給回調(diào)函數(shù)。
另外一個(gè)比較關(guān)心的問題,事件的觸發(fā)是同步的還是異步的,我們做一個(gè)實(shí)驗(yàn)
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("觸發(fā)了 data 事件!"); }); console.log("start"); eventEmitter.emit("data"); console.log("end");
上面我們我們?cè)谟|發(fā)事件前后都向控制臺(tái)打印了信息,如果觸發(fā)事件后是異步執(zhí)行的,那么后面的打印語句就會(huì)先執(zhí)行,否則如果是同步的話,就會(huì)先執(zhí)行事件綁定的回調(diào)函數(shù)。執(zhí)行結(jié)果如下
start 觸發(fā)了 data 事件! end
可見事件觸發(fā)是同步執(zhí)行的。
off
與 removeListener
方法的作用同 on
和 addLsitener
的作用是相反的,它們的作用是為某個(gè)事件刪除對(duì)應(yīng)的回調(diào)函數(shù)
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); // 第一次觸發(fā),兩個(gè)回調(diào)函數(shù)否會(huì)執(zhí)行 eventEmitter.emit("data"); eventEmitter.off("data", listener1); // 第二次觸發(fā),只會(huì)執(zhí)行 listener2 eventEmitter.emit("data");
控制臺(tái)打印結(jié)果為
listener1 listener2 listener2
第一次觸發(fā)事件時(shí),兩個(gè)事件都會(huì)觸發(fā),然后我們?yōu)槭录h除了 listener1 這個(gè)回調(diào)函數(shù),所以第二次觸發(fā)時(shí),只會(huì)觸發(fā) listener2。
注意:如果我們使用
on
或者addListener
綁定的是一個(gè)匿名函數(shù),那么便無法通過off
和removeListener
去解綁一個(gè)回調(diào)函數(shù),因?yàn)樗鼤?huì)通過比較兩個(gè)函數(shù)的引用是否相同來解綁函數(shù)的。
使用 once
可以綁定一個(gè)只執(zhí)行一次的回調(diào)函數(shù),當(dāng)觸發(fā)一次之后,該回調(diào)函數(shù)便自動(dòng)會(huì)被解綁
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("data"); }); eventEmitter.emit("data"); eventEmitter.emit("data");
上述代碼我們使用 once
為 data
事件綁定了一個(gè)回調(diào)函數(shù),然后使用 emit
方法觸發(fā)了兩次,因?yàn)槭褂?once
綁定的回調(diào)函數(shù)只會(huì)被觸發(fā)一次,所以第二次觸發(fā),回調(diào)函數(shù)不會(huì)執(zhí)行,所以在控制臺(tái)只打印了一次 data。
另外同 on
綁定的回調(diào)函數(shù)一樣,我們同樣可以通過 emit
方法向回調(diào)函數(shù)傳遞參數(shù)
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", data => { console.log(data); }); eventEmitter.emit("data", "Hello");
控制臺(tái)打印結(jié)果
Hello
使用 on
或者 addListener
為事件綁定的回調(diào)函數(shù)會(huì)被根據(jù)添加的順序執(zhí)行,而使用 prependLsitener
綁定的事件回調(diào)函數(shù)會(huì)在其他回調(diào)函數(shù)之前執(zhí)行
const {EventEmitter} = require('events'); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { console.log("on"); }); eventEmitter.prependListener("data", () => { console.log("prepend"); }); eventEmitter.emit("data");
上述代打我們先用控制臺(tái)的打印結(jié)果為
prepend on
prependOnceListener
同 prependListener
,不過它綁定的回調(diào)函數(shù)只會(huì)被執(zhí)行一次
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
綁定了一個(gè)回調(diào)函數(shù),當(dāng)觸發(fā)事件時(shí),該回調(diào)函數(shù)會(huì)在其他函數(shù)之前執(zhí)行,并且只會(huì)執(zhí)行一次,所以當(dāng)?shù)诙挝覀冇|發(fā)函數(shù)時(shí),該回調(diào)函數(shù)不會(huì)執(zhí)行,控制臺(tái)打印結(jié)果為
prepend once on on
removeAllListeners([event])
方法可以刪除事件 event
綁定的所有回調(diào)函數(shù),如果沒有傳入 event
參數(shù)的話,那么該方法就會(huì)刪除所有事件綁定的回調(diào)函數(shù)
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");
上面程序?yàn)?data
事件綁定了兩個(gè)回調(diào)函數(shù),并且在調(diào)用 removeAllListeners
方法之前分別觸發(fā)了一次 data
事件,第二次觸發(fā) data
事件時(shí),不會(huì)有任何的回調(diào)函數(shù)被執(zhí)行,removeAllListeners
刪除了 data
事件綁定的所有回調(diào)函數(shù)??刂婆_(tái)的打印結(jié)果為:
data 1 data 2
通過 eventNames
方法我們可以知道為哪些事件綁定了回調(diào)函數(shù),它返回一個(gè)數(shù)組
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' ]
如果我們將某事件的所有回調(diào)函數(shù)刪除后,此時(shí) eventNames
便不會(huì)返回該事件了
eventEmitter.removeAllListeners("error"); console.log(eventEmitter.eventNames()); // [ 'start', 'end' ]
listenerCount
方法可以得到某個(gè)事件綁定了多少個(gè)回調(diào)函數(shù)
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.on("data", () => { }); eventEmitter.on("data", () => { }); console.log(eventEmitter.listenerCount("data")); // 2
setMaxListeners
是用來設(shè)置最多為每個(gè)事件綁定多少個(gè)回調(diào)函數(shù),但是實(shí)際上是可以綁定超過設(shè)置的數(shù)目的回調(diào)函數(shù)的,不過當(dāng)你綁定超過指定數(shù)目的回調(diào)函數(shù)時(shí),會(huì)在控制臺(tái)給出一個(gè)警告
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); // 設(shè)置只能為每個(gè)回調(diào)函數(shù)綁定 1 個(gè)回調(diào)函數(shù) eventEmitter.setMaxListeners(1); // 為 data 事件綁定了三個(gè)回調(diào)函數(shù) eventEmitter.on("data", () => { console.log("data 1"); }); eventEmitter.on("data", () => { console.log("data 2"); }); eventEmitter.on("data", () => { console.log("data 3"); });
運(yùn)行上述程序,控制臺(tái)打印結(jié)果為
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
可見事件綁定的三個(gè)回調(diào)函數(shù)都可以被觸發(fā),并且在控制臺(tái)打印出了一條警告信息。
getMaxListeners
是獲得能為每個(gè)事件綁定多少個(gè)回調(diào)函數(shù)的方法,使用 setMaxListeners
設(shè)置的值時(shí)多少,返回的值就是多少
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.setMaxListeners(1); console.log(eventEmitter.getMaxListeners()); // 1
如果沒有使用 setMaxLsiteners
進(jìn)行設(shè)置,那么默認(rèn)能夠?yàn)槊總€(gè)事件最多綁定 10
個(gè)回調(diào)函數(shù),可以通過 EventEmitter
的 defaultMaxListeners
屬性獲得該值
const {EventEmitter} = require("events"); console.log(EventEmitter.defaultMaxListeners); // 10
當(dāng)我們使用 once
綁定一個(gè)回調(diào)函數(shù)時(shí),不會(huì)直接為該事件綁定該函數(shù),而是會(huì)使用一個(gè)函數(shù)包裝該函數(shù),這個(gè)包裝函數(shù)稱為 wrapper
,然后為該事件綁定 wrapper
函數(shù),在 wrapper
函數(shù)內(nèi)部,設(shè)定了當(dāng)執(zhí)行一次之后將自己解綁的邏輯。
listeners
返回指定事件綁定的回調(diào)函數(shù)組成的數(shù)組,而 rawListeners
也是返回指定事件綁定的回調(diào)函數(shù)組成的數(shù)組,與 listeners
不同的是,對(duì)于 once
綁定的回調(diào)函數(shù)返回的是 wrapper
,而不是原生綁定的函數(shù)。
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("once"); }) let fns = eventEmitter.listeners("data"); // once 綁定的函數(shù),不是 wrapper,內(nèi)部沒有解綁的邏輯,所以后面觸發(fā) data 事件時(shí)還會(huì)執(zhí)行 once 綁定的函數(shù) fns[0]() eventEmitter.emit("data");
控制臺(tái)打印結(jié)果為
once once
下面將上面的 listeners
替換為 rawListeners
const {EventEmitter} = require("events"); const eventEmitter = new EventEmitter(); eventEmitter.once("data", () => { console.log("once"); }) let fns = eventEmitter.rawListeners("data"); // 因?yàn)榉祷氐氖?nbsp;once 綁定函數(shù)的 wrapper,其內(nèi)部有執(zhí)行一次后解綁的邏輯 // 所以后面觸發(fā)事件時(shí) once 綁定的函數(shù)不會(huì)再執(zhí)行 fns[0]() eventEmitter.emit("data");
控制臺(tái)的打印結(jié)果為
once
在這個(gè)小節(jié)將從零實(shí)現(xiàn)一個(gè) EventEmitter,來加深對(duì)該模塊的理解。首先我們需要準(zhǔn)備一個(gè) listeners
來存儲(chǔ)所有綁定的回調(diào)函數(shù),它是一個(gè) Map
對(duì)象,鍵是事件名,而值是一個(gè)數(shù)組,數(shù)組中保存的是該事件綁定的回調(diào)函數(shù)。
class EventEmitter { constructor() { this.listeners = new Map(); } }
使用 on
綁定回調(diào)函數(shù)時(shí),我們先判斷 Map
集合中是否有為該事件綁定回調(diào)函數(shù),如果有取出對(duì)應(yīng)數(shù)組,并添加該回調(diào)函數(shù)進(jìn)數(shù)組,沒有則新建一個(gè)數(shù)組,添加該回調(diào)函數(shù),并添加進(jìn) Map
集合
on(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.push(callback); }
addListener
的功能與 on
是一樣的,我們直接調(diào)用 on
方法即可
addListener(event, callback) { this.on(event, callback); }
當(dāng)我們使用 emit
觸發(fā)事件時(shí),我們從 Map
取出對(duì)應(yīng)的回調(diào)函數(shù)組成的數(shù)組,然后依次取出函數(shù)執(zhí)行。另外我們還可以通過 emit
傳遞參數(shù)
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); } }
這里你可能會(huì)覺得我寫的有點(diǎn)復(fù)雜,所以你會(huì)覺得直接這么寫更好
emit(event, ...args) { if(!this.listeners.has(event)) { return; } for (let fn of fns) { fn(...args); } }一開始我也是這么寫的,但是因?yàn)?
once
綁定的函數(shù)它在執(zhí)行完畢后將自己從數(shù)組中移除,并且是同步的,所以在執(zhí)行循環(huán)的時(shí)候,數(shù)組是在不斷變化的,使用上述的方式會(huì)使得一些回調(diào)函數(shù)會(huì)被漏掉,所以我才會(huì)先將數(shù)組中的函數(shù)復(fù)制到另一個(gè)數(shù)組,然后遍歷這個(gè)新的數(shù)組,因?yàn)?once
綁定的函數(shù)它只會(huì)刪除原數(shù)組中的函數(shù),而不會(huì)刪除新的這個(gè)數(shù)組,所以新數(shù)組的長度在遍歷的過程不會(huì)改變,也就不會(huì)發(fā)生漏掉函數(shù)未執(zhí)行的情況。
實(shí)現(xiàn) prependListener
的邏輯同 on
一樣,不過我們是往數(shù)組的最前方添加回調(diào)函數(shù)
prependListener(event, callback) { if(!this.listeners.has(event)) { this.listeners.set(event, []); } let fns = this.listeners.get(event); fns.unshift(callback); }
使用 off
方法是用來解綁事件的,在數(shù)組中找到指定的函數(shù),然后刪除即可
off(event, callback) { if(!this.listeners.has(event)) { return; } let fns = this.listeners.get(event); // 找出數(shù)組中的回調(diào)函數(shù),然后刪除 for (let i = 0; i < fns.length; i++) { if(fns[i] === callback) { fns.splice(i, 1); break; } } // 如果刪除回調(diào)函數(shù)后,數(shù)組為空,則刪除該事件 if (fns.length === 0) { this.listeners.delete(event); } }
removeListener
同 off
的作用一樣,我們?cè)趦?nèi)部直接調(diào)用 off
方法即可
removeListener(event, callback) { this.off(event, callback); }
使用 once
綁定一個(gè)只執(zhí)行一次的函數(shù),所以我們需要將綁定的回調(diào)函數(shù)使用一個(gè)函數(shù)包裝一下,然后添加進(jìn)數(shù)組中,這個(gè)包裝函數(shù)我們稱之為 wrapper
。在包裝函數(shù)中,當(dāng)執(zhí)行一遍后會(huì)將自己從數(shù)組中刪除
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
的實(shí)現(xiàn)同 once
,只是向數(shù)組的開頭插入函數(shù),將上面代碼中的 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); }
直接從刪除對(duì)應(yīng)的事件,如果沒有傳入具體事件的話,則需要?jiǎng)h除所有的事件
removeAllListeners(event) { // 如果沒有傳入 event,則刪除所有事件 if (event === undefined) { this.listeners = new Map(); return; } this.listeners.delete(event); }
獲得已經(jīng)綁定了哪些事件
eventNames() { return [...this.listeners.keys()]; }
獲得某事件綁定可多少個(gè)回調(diào)函數(shù)
listenerCount(event) { return this.listeners.get(event).length; }
上述的實(shí)現(xiàn)有一個(gè) bug,那就是無法刪除使用
once
綁定的函數(shù),我的想法是使用一個(gè)Map
將once
綁定的函數(shù)同對(duì)應(yīng)的wrapper
對(duì)應(yīng),刪除時(shí)即可根據(jù)once
的回調(diào)函數(shù)找到對(duì)應(yīng)的wrapper
然后刪除constructor() { this.listeners = new Map(); // 保存 once 的回調(diào)函數(shù)與對(duì)應(yīng)的 wrapper this.onceToWrapper = new Map(); } once(event, callback) { let wrapper = (...args) => { callback(...args); // 刪除之前,刪除 callback 和 wrapper 的關(guān)系 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 的關(guān)系 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 中查找是否有對(duì)應(yīng)的 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; } }
到此,相信大家對(duì)“Node.js中的EventEmitter模塊怎么使用”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!