釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

言sir發表於2018-06-27

小插曲

事件

  • 建議大家看下官網中events事件的描述node中events事件
  • 釋出訂閱模式定義了一種一對多的依賴關係
  • 在Node中EventEmitter開放on(事件名,回撥函式)用於訂閱事件
  • emit(事件名)用於釋出事件,可能對應多個訂閱事件,讓訂閱事件依次執行

不明白?沒關係。舉個最簡單例子,女人失戀了會哭,還會找新男朋友,在這裡哭和找男朋友相當於訂閱女人失戀的回撥,什麼時候執行呢?當釋出女人失戀這件事的時候,說的這麼抽象,直接來一段程式碼吧

釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

  • 是不是很簡單,只有釋出這個事件時候,被訂閱的事件才會依次執行,形成一對多的依賴關係。接下來直接寫原始碼實現

思路構建

  • 我們想構造一個類似這樣的物件 {"失戀":[cry,findBoy]},當事件釋出的時候,讓陣列中對應的函式依次執行,就實現了這樣的效果
  • 先講個小知識點 {}和Object.create(null)區別。 {}有作用鏈,通過Object.create(null)創造的空物件沒有作用鏈,給大家演示下,其它就沒啥區別。原始碼就是這樣寫(逼格高)

釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

實現events模組

1、on和emit 兩個核心方法

  • 原始碼實現
// 宣告EventEmitter事件發生器建構函式
function EventEmitter() {
    this._events = Object.create(null);
}
//on 訂閱方法實現  因為在例項上呼叫,所以寫在原型上
EventEmitter.prototype.on = function(type,callback){
    // 如果例項不存在則建立一個空物件,Object.create(null)沒有鏈
    if(!this._events) {
        this._events = Object.create(null);
    }
    if(this._events[type]){ //如果失戀有對應的值,直接往陣列push
        this._events[type].push(callback)
    }else { //第一次訂閱,沒有失戀,就宣告{失戀:[cry]}
        this._events[type] = [callback];
    }
};
// emit方法實現
EventEmitter.prototype.emit = function(type){
    if(this._events[type]){ //{失戀:[cry,eat]} 如果失戀對應有值,依次執行裡面的方法
        this._events[type].forEach(fn=>fn())
    }
};
module.exports = EventEmitter
複製程式碼
  • 十幾行程式碼就實現核心功能,這麼簡單?對 就是這麼簡單,趕快來測試下吧

釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

2、removeListener 取消訂閱事件,失戀了不想哭了,所以我們提供個移除監聽的方法

  • 比較簡單,直接上程式碼吧看的直接
// 移除訂閱事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
    if(this._events[type]){
        // 返回false就表示不要了,用filter實現去重
        this._events[type] = this._events[type].filter(fn=>fn!==callback)
    }
};
複製程式碼
  • 測試下吧,失戀了不想哭了
    釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會
  • 完美實現,是不是很激動。

3、removeAllListeners移除全部的監聽器,與removeListener相對應

// removeAllListeners 移除所有的監聽者
EventEmitter.prototype.removeAllListeners = function(){
//簡單粗暴,直接賦值空物件 {}
    this._events = Object.create(null);
};
複製程式碼
  • 測試下,失戀了既不想哭,也不想找物件,什麼也不列印就對拉

釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

4、擴充套件once方法 我們希望哭的事件 多次釋出emit時候只執行一次,也就代表執行一次後需要將事件從對應關係中移除掉。

// once實現
EventEmitter.prototype.once = function(type,callback,flag){
    // 先繫結 呼叫後再刪除,運用了one函式 {失戀:one}
    let one = (...args)=> {
        callback(...args);
        this.removeListener(type, one);
    }
    //自定義屬性 因為例項中沒有one屬性
    one.l = callback;
    this.on(type,one)
};
// 移除訂閱事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
    if(this._events[type]){
        // 返回false就表示不要了,用filter實現去重
        this._events[type] = this._events[type].filter(fn=>fn!==callback && fn.l!==callback)
    }
};
複製程式碼
  • 你可能會疑惑為什麼宣告一個wrap函式,設想下,不然你告訴我怎麼先繫結一次,在移除。很多人可能都會這麼寫
  • 錯誤例子 錯誤例子 錯誤例子(重要事情說三遍)
// - 錯誤例子 錯誤例子 錯誤例子(重要事情說三遍)
//你可能會這麼寫,但剛繫結就移除拉,體會這意思了吧
EventEmitter.prototype.once = function(type,callback){
//先繫結在移除
    this.on(type,callback);
    this.removeListener(type,callback)
};
複製程式碼
  • 測試下吧,一步一個腳印

釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

5、newListener方法。當cry新增到內部監聽陣列({失戀:[cry]})之前,會觸發自身的'newListener'事件

  • 沒聽懂?我們先來看官方的用法
    釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

簡單說就是可以監控到訂閱的事件型別,上原始碼看下如何實現

//on 訂閱方法實現  因為在例項上呼叫,所以寫在原型上
EventEmitter.prototype.on = function(type,callback){
    // 如果例項不存在則建立一個空物件,Object.create(null)沒有鏈
    if(!this._events) {
        this._events = Object.create(null);
    }
    if(type!=="newListener"){
        if(this._events["newListener"]){
            this._events["newListener"].forEach(fn=>fn(type))
        }
    }
    if(this._events[type]){ //如果失戀有對應的值,直接往陣列push
        this._events[type].push(callback)
    }else { //第一次訂閱,沒有失戀,就宣告{失戀:[cry]}
        this._events[type] = [callback];
    }
};
複製程式碼
  • 測試下吧

釋出訂閱模式還不會??戳這裡,50行核心程式碼,手把手教你學會

看到這裡,基本方法都實現了。不常用就不解釋拉。 如果大家想看所有原始碼方法解析,可以點進我github上參考

相關文章