日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
EventEmitter的核心功能實(shí)現(xiàn)

大家好,我是前端西瓜哥。

成都創(chuàng)新互聯(lián)專注于企業(yè)全網(wǎng)整合營銷推廣、網(wǎng)站重做改版、東海網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5建站、商城網(wǎng)站開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為東海等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

EventEmitter 是頻率較高的前端面試題。

EventEmitter 是 Nodejs 環(huán)境下才能使用的庫,所以不能直接用于瀏覽器環(huán)境的開發(fā)。所以我考慮自己實(shí)現(xiàn)一套邏輯,自己定制的話也容易根據(jù)實(shí)際情況的變動(dòng)做修改。

因此我決定了解一下 EventEmitter 的 API,并嘗試自己實(shí)現(xiàn)一套邏輯。

Nodejs 的 EventEmitter API

首先當(dāng)然是要了解需求,即 EventEmitter 的 API 使用。詳細(xì)使用方式請(qǐng)查閱 官方文檔,我這里只簡單敘述一些常用的 API。

const { EventEmitter, errorMonitor } = require('events');
// 創(chuàng)建事件觸發(fā)器實(shí)例
const emitter = new EventEmitter()
// on:注冊(cè)監(jiān)聽者函數(shù)??梢宰?cè)多個(gè)監(jiān)聽函數(shù),
// 觸發(fā)事件后,會(huì)依次同步執(zhí)行,順序?yàn)榻壎〞r(shí)的順序。
// 別名為:addListener
emitter.on('event', function(a, b) => {
console.log('event emit!', a, b)
})
// once:注冊(cè)一個(gè)只會(huì)被執(zhí)行一次的函數(shù)
emitter.once('event', function() => {
console.log('event emit only once!')
})
// emit:觸發(fā)事件,可提供參數(shù)。
// 如果有對(duì)應(yīng)監(jiān)聽器函數(shù),會(huì)返回 true,否則返回 false
emitter.emit('event', 3, 4)
// 比較特別的是,如果沒有注冊(cè) error 事件的監(jiān)聽者,
// 觸發(fā) error 時(shí),錯(cuò)誤不會(huì)被捕獲而直接報(bào)錯(cuò);
// 若注冊(cè),則錯(cuò)誤會(huì)被捕獲
emitter.emit('error', new Error('whoops!'))
// event.errorMonitor 是一個(gè) Symbol
// 能夠在觸發(fā) error 事件時(shí),先執(zhí)行被綁定的監(jiān)視器函數(shù)
emitter.on(errorMonitor, err => {
console.log('error monitor')
});
// 移除指定監(jiān)聽器,別名為:removeListener
emitter.off(eventName, handler)
// 獲取注冊(cè)的事件名的數(shù)組形式
emitter.eventNames()
  • 監(jiān)聽者函數(shù)的 this 會(huì)指向 EventEmitter 實(shí)例。當(dāng)然你可以使用各種方法修改 this 的指向,如箭頭函數(shù)或 bind 方法。
  • 每次添加監(jiān)聽器時(shí),都會(huì)觸發(fā) newListener 事件,傳入的參數(shù)為事件名(eventName)和監(jiān)聽器函數(shù)(listener)。
  • 同樣,移除監(jiān)聽器時(shí),會(huì)觸發(fā) removeListener 事件。
  • emitter.prependListener():同 on,但會(huì)添加到監(jiān)聽器數(shù)組的開頭。
  • ...

API 很多,但我不打算實(shí)現(xiàn)了這么多,就只實(shí)現(xiàn)最常用的 on、emit、off。

實(shí)現(xiàn)

首先,我們知道不同的事件是有特定的 eventName(事件名)的,通過指定 eventName,我們才能綁定對(duì)應(yīng)的多個(gè)監(jiān)聽器(函數(shù)),才能觸發(fā)事件執(zhí)行綁定的這些監(jiān)聽器。

這時(shí)候,我們就涉及到數(shù)據(jù)結(jié)構(gòu)與算法的存儲(chǔ)問題了。因?yàn)榻Y(jié)構(gòu)和算法是相輔相成的,選擇不同的數(shù)據(jù)結(jié)構(gòu),使用的算法就會(huì)不同。

不同的數(shù)據(jù)結(jié)構(gòu)與算法的優(yōu)點(diǎn)的缺陷各不相同,比如空間復(fù)雜度上或時(shí)間復(fù)雜度上的效率不同。

listener 函數(shù)的存儲(chǔ)

那么如何存儲(chǔ)呢?常見的方法是使用哈希表,因?yàn)闀r(shí)間復(fù)雜度是 O(1),空間復(fù)雜度一般也不會(huì)太大。JavaScript 的對(duì)象本質(zhì)上就是哈希表。所以我們的存儲(chǔ)方式是:

this.hashMap = {
'event1': [listener1, listenr2],
'event2': [],
}

一些可擴(kuò)展的點(diǎn):

  • 哈希表的一個(gè)問題是:無序??梢酝ㄟ^額外使用一個(gè)數(shù)組來記錄添加 eventName 的記錄順序。這樣的話,實(shí)現(xiàn) emitter.eventNames() 可以拿到有序的事件數(shù)據(jù)。當(dāng)然這樣的需求比較少見,這里只是簡單提一下。
  • 如果要實(shí)現(xiàn) once(設(shè)置執(zhí)行一次就不再執(zhí)行的監(jiān)聽器函數(shù)),則需要對(duì)函數(shù)標(biāo)記,這時(shí)候可以考慮讓數(shù)組元素的格式改為 { listener: Listener, once: boolean },在觸發(fā)事件的時(shí)候,執(zhí)行監(jiān)聽器函數(shù)時(shí),將 once 值為 true 的監(jiān)聽器從數(shù)組中移除。
  • 可以改為鏈表實(shí)現(xiàn)存儲(chǔ),這樣移除中間監(jiān)聽器時(shí),時(shí)間復(fù)雜度可以變成 O(1)。另外數(shù)組刪除元素的時(shí)間復(fù)雜度是 O(n)。但會(huì)引入實(shí)現(xiàn)上的復(fù)雜度,因?yàn)闆]有內(nèi)置的鏈表實(shí)現(xiàn),需要自己手動(dòng)實(shí)現(xiàn)一個(gè)沒有 BUG 的鏈表類。

on() 的實(shí)現(xiàn)

on() 的實(shí)現(xiàn),其實(shí)就是將監(jiān)聽器函數(shù)綁定到指定事件對(duì)應(yīng)的數(shù)組中。實(shí)現(xiàn)起來并不難,只要注意如果是第一次添加指定事件時(shí),要先初始化一個(gè)空數(shù)組即可。on 最后返回了 this,是為了實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。

class EventEmiter {
on(eventName, listener) {
if (!this.hashMap[eventName]) {
this.hashMap[eventName] = []
}
this.hashMap[eventName].push(listener)
return this
}
}

off() 的實(shí)現(xiàn)

off() 會(huì)根據(jù)傳入的事件名,找到對(duì)應(yīng)的監(jiān)聽器數(shù)組,從中移除指定監(jiān)聽器。同樣為了實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用返回了 this。

class EventEmiter {
off(eventName, listener) {
const listeners = this.hashMap[eventName]
if (listeners && listeners.length > 0) {
const index = listeners.indexOf(listener)
if (index > -1) {
listeners.splice(index, 1)
}
}
return this
}
}

emit() 的實(shí)現(xiàn)

emit() 的實(shí)現(xiàn)很簡單,找到事件對(duì)應(yīng)的監(jiān)聽器,傳入?yún)?shù)依次執(zhí)行。如果事件沒有綁定監(jiān)聽器,返回 false。否則,返回 true。

class EventEmiter {
emit(eventName, ...args) {
const listeners = this.hashMap[eventName]
if (!listeners || listeners.length === 0) return false
listeners.forEach(listener => {
listener(...args)
})
return true
}
}

完整實(shí)現(xiàn)

雖然很突然,我這里給出的是 TypeScript 實(shí)現(xiàn),只要將類型聲明去掉就是 JavaScript 實(shí)現(xiàn)了。當(dāng)然下面代碼是做了簡單的單元測試的,大概是沒問題的。

源碼地址:

type EventName = string | symbol
type Listener = (...args: any[]) => void
class EventEmiter {
private hashMap: { [eventName: string]: Array } = {}
on(eventName: EventName, listener: Listener): this {
const name = eventName as string
if (!this.hashMap[name]) {
this.hashMap[name] = []
}
this.hashMap[name].push(listener)
return this
}
emit(eventName: EventName, ...args: any[]): boolean {
const listeners = this.hashMap[eventName as string]
if (!listeners || listeners.length === 0) return false
listeners.forEach(listener => {
listener(...args)
})
return true
}
off(eventName: EventName, listener: Listener): this {
const listeners = this.hashMap[eventName as string]
if (listeners && listeners.length > 0) {
const index = listeners.indexOf(listener)
if (index > -1) {
listeners.splice(index, 1)
}
}
return this
}
}

因?yàn)閷?duì)象不支持 Symbol 作為索引,所以這里的實(shí)現(xiàn)做了類型的強(qiáng)轉(zhuǎn)。未來,TypeScript 可能會(huì)允許對(duì)象索引為 Symbol,Enum 等,但目前不行。


新聞名稱:EventEmitter的核心功能實(shí)現(xiàn)
URL地址:http://www.5511xx.com/article/ccojheh.html