前言
這是系統(tǒng)性學(xué)習(xí) Nodejs 的第三篇乞娄,所有文章都會收錄在我的專欄里面衅码,歡迎大家關(guān)注咧最。
使用
events 模塊是 Node.js 的核心模塊翩腐,而大多是核心模塊都繼承自 events 模塊叠殷,例如:net.Server改鲫、fs.ReadStream 等待。所以 events 模塊非常重要林束。
其實(shí) events 就是我們常見的發(fā)布訂閱模式像棘,我們看一下它 Node 里面是如果用的。
這是一個(gè)簡單的示例壶冒,myEmitter.on 為一個(gè)事件注冊監(jiān)聽器缕题,myEmitter.emit 觸發(fā)事件,從而執(zhí)行監(jiān)聽器胖腾。on 方法可以注冊多個(gè)監(jiān)聽器烟零,同一個(gè)事件的監(jiān)聽器都會放在一個(gè)數(shù)組里。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on('event', () => {
console.log('觸發(fā)事件');
});
myEmitter.emit('event');
傳遞參數(shù)
const myEmitter = new MyEmitter();
myEmitter.on('event', function(a, b) {
console.log(a, b);
// 打酉套鳌:
// a b
});
myEmitter.emit('event', 'a', 'b');
調(diào)用 emit 方法時(shí)锨阿,可以傳入任意數(shù)量的參數(shù)。
off
移除監(jiān)聽器
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
const fn = () => {
console.log('觸發(fā)事件');
}
myEmitter.on('event', fn);
myEmitter.off('event', fn);
myEmitter.emit('event');
// 不會再打印 '觸發(fā)事件'
once
添加單次監(jiān)聽器到名為 eventName 的事件记罚。 當(dāng) eventName 事件下次觸發(fā)時(shí)墅诡,監(jiān)聽器會先被移除,然后再調(diào)用桐智。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
const fn = () => {
console.log('觸發(fā)事件');
}
myEmitter.once('event', fn);
myEmitter.emit('event');
myEmitter.emit('event');
// 雖然觸發(fā)觸發(fā)了兩次 event末早,但只打印了一次 evnet。
手寫 EventEmitter
用法了解完了说庭,我們手寫吧然磷。來吧,展示刊驴。
初始化
首先 EventEmitter 是個(gè) class, 所以代碼如下姿搜。
class EventEmitter {
constructor() {
}
}
每個(gè) events 可以通過 on 來監(jiān)聽事件,每個(gè)事件可以注冊多個(gè)監(jiān)聽器捆憎,所以我們要有一個(gè)東西來存儲這些事件舅柜。我們可以通過這樣的數(shù)據(jù)結(jié)果來存儲。
events: {
'eventName1': [
function listener1() {},
function listener2() {}
]
...
}
所以我們需要在 constructor 內(nèi)初始化 events 屬性攻礼。
class EventEmitter {
constructor() {
this._events = {}
}
}
on
on 方法是給一個(gè)事件注冊一個(gè)監(jiān)聽器业踢,其實(shí)就是在 events 上增加屬性栗柒,通過上面分析的 events 數(shù)據(jù)結(jié)構(gòu)礁扮,我們很容易寫出 on 的代碼知举。
on(eventName, listeners) {
if (this._events[eventName]){
this._events[eventName].push(listeners)
} else {
this._events[eventName] = [listeners]
}
}
可以看到代碼是相當(dāng)?shù)暮唵危绻?dāng)前事件已經(jīng)注冊過監(jiān)聽器太伊,則直接 push雇锡;如果沒有注冊過,則創(chuàng)建一個(gè)新的數(shù)組僚焦,并把監(jiān)聽器放進(jìn)去锰提。
emit
emit 方法其實(shí)也特別簡單,其實(shí)就是通過事件名找到對應(yīng)的監(jiān)聽器數(shù)組芳悲,然后遍歷執(zhí)行即可立肘。
emit(eventName,...args) {
if(this._events[eventName]){
this._events[eventName].forEach(fn=>fn(...args))
}
}
off
off 方法就是根據(jù)事件名從 events 內(nèi)找到對應(yīng)的監(jiān)聽器,然后刪除掉名扛。
off(eventName,listener) {
if(!this._events[eventName]) return
this._events[eventName] = this._events[eventName].filter(fn=>(fn !== listener))
}
once
只綁定一次谅年。實(shí)現(xiàn)起來也超簡單,就是在執(zhí)行監(jiān)聽器的時(shí)候就把當(dāng)前監(jiān)聽器移除掉肮韧∪邗澹看到代碼你就懂了。
once(eventName,listener) {
const wrapListener = (...args) =>{
listener(...args);
// 當(dāng)綁定后將自己移除掉
this.off(eventName,once);
}
this.on(eventName,wrapListener)
}
```javaScript
似不似超簡單弄企,但是這樣就完了嗎超燃?我們看下這個(gè)例子
```javaScript
const EventEmitter = require('events');
const fn = () => {
console.log('觸發(fā)事件');
}
myEmitter.once('event', fn);
myEmitter.off('event', fn)
myEmitter.emit('event');
我們先 once 綁定一個(gè)事件,然后又通過 off 移除了事件拘领,下面 emit 的時(shí)候意乓,理論上不應(yīng)再執(zhí)行 fn 了,但實(shí)際上院究,fn 執(zhí)行了锣披。這是為什么呢韧掩?
因?yàn)?once 時(shí)注冊的監(jiān)聽器是我們內(nèi)部自己創(chuàng)建的監(jiān)聽器(wrapListener),而不是這里的 fn,所以移除 fn 肯定時(shí)不行的择诈。所以我們要修改下我們的 once 代碼。
once(eventName, listener) {
const wrapListener = (...args) =>{
listener(...args);
// 當(dāng)綁定后將自己移除掉
this.off(eventName,once);
}
wrapListener.origin = listener
this.on(eventName, wrapListener)
}
```javaScript
這里我們原本的 listener 保存到 wrapListener.origin 上面晌缘。
然后在 off 時(shí)长捧,我們判斷如果 wrapListener.origin 等于要被移除的監(jiān)聽器,這時(shí)候我們也就此監(jiān)聽器移除掉放祟。代碼如下:
```javaScript
off(eventName,listener) {
if(!this._events[eventName]) return
this._events[eventName] = this._events[eventName].filter(fn=>(fn !== listener && fn.origin !== listener))
}