這幾種模式是基於訂閱觀察者模式的,維護一個事件中心,on的時候將事件按名稱存在事件中內心,稱之爲訂閱者,而後emit將對應的事件進行發佈,去執行事件中內心的對應的監聽器。javascript
第一步就是建立一個構造構造,維護一個事件中心eventsjava
function EventEmiter(){ this.events = {} }
$on數組
//event能夠是事件名數組 EventEmiter.prototype.on = function(event,cb){ //多個事件 if(event instanceof Array){ event.forEach(fn=>this.on(fn,cb)) } //單個事件 if(this.events[event]){ this.events[event].push(cb) }else{ this.events[event] = [cb] } }
$emitapp
//cb 參數:單個事件名,args參數 this.emit('evt',a,b,c) EventEmiter.prototype.emit = function(event){ let args = Array.from(arguments).slice(1) let cbs = this.events[event]; if(cbs){ cbs.forEach(cb=>cb.apply(this,args)) } }
$once函數
// 事件回調執行一次就清除事件,參數:單個事件名,單個監聽器 EventEmiter.prototype.once = function(event,cb){ function oneTime(){
//先執行回調,而後清除該事件的對應回調 cb.apply(this,arguments) this.off(event,cb) }
//on函數的fn屬性添加一個標記,cb,方便循環off清除(提供了事件與回調的時候) oneTime.cbName = cb; this.on(event,oneTime); }
$offthis
/*移除自定義事件監聽器。 若是沒有提供參數,則移除全部的事件監聽器; 若是隻提供了事件,則移除該事件全部的監聽器; 若是同時提供了事件與回調,則只移除這個回調的監聽器。 */ EventEmiter.prototype.off = function(event,cb){ if(!arguments){ this.events = Object.create(null) } if(event instanceof Array){ event.forEach(evt=>this.off(evt,cb)) } if(!cb){ this.events[event] = null } if(cb){ let cbs = this.events[event] if(cbs){ for(let i = 0;i<cbs.length;i++){ if(cb === cbs[i] || cb === cbs[i].cbName){ cbs.splice(i,1) break } } } } }
總結:其實原理很是簡單,要注意的是$once 不是直接$on提交對應的回調函數,而是包裝成另外的On函數,On函數做爲回調Push進事件中心。On函數自己的做用是執行一次事件的回調,而後就立馬$off去出該事件的監聽回調。同時,On函數已經不是原來的cb回調了,因此爲了待會$off的時候能準確找到背後的那個cb,因此給On函數添加了屬性方便找到它spa