手寫 events 模塊

前言

這是系統(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))
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鳍怨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子跪妥,更是在濱河造成了極大的恐慌鞋喇,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眉撵,死亡現(xiàn)場離奇詭異侦香,居然都是意外死亡落塑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門罐韩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憾赁,“玉大人,你說我怎么就攤上這事散吵×迹” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵矾睦,是天一觀的道長晦款。 經(jīng)常有香客問我,道長枚冗,這世上最難降的妖魔是什么柬赐? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮官紫,結(jié)果婚禮上肛宋,老公的妹妹穿的比我還像新娘。我一直安慰自己束世,他們只是感情好酝陈,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著毁涉,像睡著了一般沉帮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贫堰,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天穆壕,我揣著相機(jī)與錄音,去河邊找鬼其屏。 笑死喇勋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偎行。 我是一名探鬼主播川背,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蛤袒!你這毒婦竟也來了熄云?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤妙真,失蹤者是張志新(化名)和其女友劉穎缴允,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珍德,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡练般,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年健蕊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踢俄。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晴及,靈堂內(nèi)的尸體忽然破棺而出都办,到底是詐尸還是另有隱情,我是刑警寧澤虑稼,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布琳钉,位于F島的核電站,受9級特大地震影響蛛倦,放射性物質(zhì)發(fā)生泄漏歌懒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一溯壶、第九天 我趴在偏房一處隱蔽的房頂上張望及皂。 院中可真熱鬧,春花似錦且改、人聲如沸验烧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碍拆。三九已至,卻和暖如春慨蓝,著一層夾襖步出監(jiān)牢的瞬間感混,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工礼烈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弧满,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓此熬,卻偏偏與公主長得像谱秽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子摹迷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349