發佈訂閱模式---模擬Node中的EventEmitter

發佈 — 訂閱模式

發佈 — 訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀 態發生改變時,全部依賴於它的對象都將獲得通知。緩存

在 JavaScript開發中,咱們通常用事件模型來替代傳統的發佈 — 訂閱模式。bash

實現的關鍵要素

  • 發佈者有一個訂閱者緩存隊列
  • 發佈者有增長和刪除訂閱者的方法
  • 發佈者狀態改變,須要notify方法通知隊列中的全部訂閱者
  • js中採用事件回調的方式來更新訂閱者,所以訂閱者再也不須要update方法

下面來模擬下EventEmitter的初步實現app

class EventEmitter {
    constructor() {
        this._events = {};//用對象的方式來緩存訂閱者隊列(事件名稱:回調)
    }

    on(eventName, listener) {
        if(typeof listener !== 'function') { return; }
        
        if(!this._events) {//若是隻被繼承了prototype,須要在繼承的對象上添加_events屬性
            this._events = Object.create(null);
        }

        if(!this._events[eventName]) {//事件隊列不存在
            this._events[eventName] = [];
        }

        this._events[eventName].push(listener);//添加觀察者
    }

    addListener(eventName, listener) {
        this.on(eventName, listener);
    }

    removeListener(eventName, listener) {
        if(!this._events[eventName]) { return; }

        this._events[eventName] = this._events[eventName].forEach(item => {
            return item !== listener;
        });
    }

    emmit(eventName, ...args) {//狀態改變
        if(!this._events[eventName]) { return; }

        this._events[eventName].forEach(callback => {//通知全部的訂閱者,發起回調
            callback.apply(this, args);
        });
    }
}
複製代碼

EventEmitter中的once方法能夠作到綁定的事件只調用一次,以後不會再被調用,他的實現方式實在怎麼樣的?正常狀況應該是在回調函數被調用一次以後移除這個回調。能夠考慮在回調函數上加上once屬性,在發起回調的時候判斷once是否爲真,來肯定是否移除這個回調。這樣能夠達到目的,可是在發起回調時,須要每一次都判斷,給通知方法增長了額外的負擔,來考慮一個更聰明的實現方式。函數

wrap函數

once(eventName, listener) {
    function wrap(args) {
        listener.apply(this, args);
        this.removeListener(eventName, wrap);
    }

    wrap.cb = listener;//將回調存儲起來用於刪除時對比

    this.on(eventName, wrap);
}
複製代碼

將回調函數包裹起來,在包裹函數內部移除原回調函數,而後將wrap函數添加進觀察者隊列。同時要將原回調函數存進wrap中,用在在移除原回調時判斷。ui

修改移除觀察者方法

removeListener(eventName, listener) {
    if(!this._events[eventName]) { return; }

    this._events[eventName] = this._events[eventName].forEach(item => {
        return item !== listener && item.cb !== listener;
    });
}
複製代碼

newListener事件

比較有趣的是EventEmitter同時提供了newListener事件,每次添加觀察者(即便是第二次添加newListener)時都會觸發這個事件,在on方法中須要添加以下代碼:this

this.emmit('newListener', eventName, listener);//觸發newListener事件回調
複製代碼

defaultMaxListeners

這個靜態屬性限制了一種事件能夠添加的最大回調數量,同時還有配套的setMaxListeners和getMaxListeners方法來設置和獲取每一個事件能夠添加的最大回調數量spa

setMaxListeners(n) {
    this.maxListeners = n;
}

getMaxListeners() {
    return this.maxListeners ? this.maxListeners : EventEmitter.defaultMaxListeners;
}
複製代碼

on方法添加判斷;prototype

if(this._events[eventName].length > this.getMaxListeners()){
    console.warn('超過最大數量,請修改maxListeners')
}
複製代碼

完整代碼

class EventEmitter {
    constructor() {
        this._events = {};//用對象的方式來緩存訂閱者隊列(事件名稱:回調)
    }

    setMaxListeners(n) {
        this.maxListeners = n;
    }

    getMaxListeners() {
        return this.maxListeners ? this.maxListeners : EventEmitter.defaultMaxListeners;
    }

    on(eventName, listener) {
        if(typeof listener !== 'function') { return; }

        if(!this._events) {//若是隻被繼承了prototype,須要在繼承的對象上添加_events屬性
            this._events = Object.create(null);
        }

        this.emmit('newListener', eventName, listener);//觸發newListener事件回調

        if(!this._events[eventName]) {//事件隊列不存在
            this._events[eventName] = [];
        }

        this._events[eventName].push(listener);//添加觀察者

        if(this._events[eventName].length > this.getMaxListeners()){
            console.warn('超過最大數量,請修改maxListeners')
        }
    }

    once(eventName, listener) {
        function wrap(args) {
            listener.apply(this, args);
            this.removeListener(eventName, wrap);
        }

        wrap.cb = listener;//將回調存儲起來用於刪除時對比

        this.on(eventName, wrap);
    }

    addListener(eventName, listener) {
        this.on(eventName, listener);
    }

    removeListener(eventName, listener) {
        if(!this._events[eventName]) { return; }

        this._events[eventName] = this._events[eventName].forEach(item => {
            return item !== listener && item.cb !== listener;
        });
    }

    emmit(eventName, ...args) {//狀態改變
        if(!this._events[eventName]) { return; }

        this._events[eventName].forEach(callback => {//通知全部的訂閱者,發起回調
            callback.apply(this, args);
        });
    } 
}
複製代碼
相關文章
相關標籤/搜索