一道面試題
最近在看《JavaScript設計模式與開發實踐》中的【發佈訂閱模式和觀察者模式】。我不禁想起了上半年面試的時候一個面試官問我的問題:“你在實際項目中是如何處理非父子組件通信的?”
我回答的是:“大型項目的話一般都會用vuex,在一些小場景裏會用EventEmitter”。
沒想到面試官接着來了一句:“那你能手寫代碼實現一個簡單的EventEmitter嗎?”
手寫WventEmitter
我想了一下,這主要是使用了emit發事件,用on去監聽,還有off銷燬事件監聽,once實現單次事件處理…等等。考慮到時間緊張,我就只實現了收、發事件,移除監聽的功能,有驚無險。。。
其實細想,這個和vue中內置實現的
$emit
、$on
是差不多的
且看下面代碼:
class EventEmitter{
constructor() {
// 維護事件及監聽者
this.listenners={}
}
/**
* 註冊事件監聽者
* @param {Object} type 事件類型
* @param {Object} cb 回調函數
*/
on(type,cb){
if(!this.listenners[type]){
this.listenners[type]=[]
}
this.listenners[type].push(cb)
}
/**
* 發佈事件
* @param {String} type 事件類型
* @param {Function} cb 回調函數
*/
emit(type,...args){
if(this.listenners[type]){
this.listenners[type].forEach(cb=>{
cb(...args)
})
}
}
/**
* 移除某個事件的一個監聽者
* @param {Object} type 事件類型
* @param {Object} cb 回調函數
*/
off(type,cb){
if(this.listenners[type]){
const targetIndex=this.listenners[type].findIndex(item=>item===cb)
if(targetIndex!==-1){
this.listenners[type].splice(targetIndex,1)
}
if(this.listenners[type].length===0){
delete this.listenners[type]
}
}
}
/**
* 移除某個事件的所有監聽者
* @param {Object} type 事件類型
*/
offAll(type){
if(this.listenners[type]){
delete this.listenners[type]
}
}
}
有了這個自己實現的簡單版本的EventEmitter
,我們就不用依賴第三方庫了!
const mxc=new EventEmitter()
mxc.on('mxc',function(address,food){console.log('我餓了,我們取${address}吃${food}!')})
mxc.emit('mxc','南門','小火鍋')
對了,這和Vue的:
const ee = new Vue();
ee.$on('chifan', function(address, food) { console.log(`吃飯了,我們去${address}吃${food}!`) })
ee.$emit('chifan', '三食堂', '鐵板飯')
可是有異曲同工之妙!
再往下考慮就會發現,EventEmitter就是一個典型的發佈訂閱模式,實現了事件調度中心。
發佈訂閱模式中,包含發佈者、事件調度中心、訂閱者三個角色,我們剛剛實現的EventEmitter的一個實例:mxc,就是一個事件調度中心,發佈者和訂閱者之間是互不關心的,它們是鬆散耦合的。它們關注事件本身。
若有不懂之處,筆者還準備瞭如下文章用以解惑: