javascript設計模式之發佈訂閱模式

  發佈-訂閱設計模式對你們來講並非很陌生,舉一個最簡單的例子,在前端開發過程當中,事件的綁定就是其實際的應用。首先咱們先了解下什麼是發佈-訂閱模式。前端

    基本概念:發佈-訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴它的對象都獲得通知。在前端開發中,咱們通常用事件模型來替代傳統的發佈-訂閱模式。編程

  發佈-訂閱模式是前端經常使用的一種設計模式,如今主流的MVVM框架,都大量使用了此設計模式,其主要做用有如下兩點:一是能夠實現模塊間通訊,而是能夠在必定程度上實現異步編程。其基本事件模型以下:設計模式

  前端的事件綁定有三要素,一是傳入事件類型,二是聲明對應的回調方法,三是觸發條件;觸發條件爲對應的事件類型。前端DOM的事件系統本質也是發佈-訂閱模式,而咱們在業務處理中所應有的模式也與此相似,只不過發佈訂閱模式應用的是自定義事件類型,能夠自定義。緩存

  發佈訂閱事件模型本質上就是一個中央事件處理總線,它接收全部訂閱的自定義事件,並將自定義事件的回調存儲到事件回調的堆棧中,當某在某個時刻觸發了此自定義事件,會調用分發事件的方法,從事件回調堆棧中取出符合條件的事件回調,依次調用,具體實現邏輯以下:app

class EventEmiter {
    constructor() {
            //回調中心
            this.listenerList = {};
            this.createEventList = {};
        }
        /**
         * 添加事件監聽
         * @param {事件類型} type
         * @param {回調方法} fn
         */
    on(type, fn) {
            if (!this.listenerList[type]) {
                this.listenerList[type] = [];
            }
            this.listenerList[type].push(fn);
        }
        /**
         * 觸發事件監聽
         * @param {事件類型} type
         */
    emit(type, flag) {
            let fnList = this.listenerList[type];
            let params = Array.from(arguments);
            if (!fnList || fnList.length === 0) {
                return false;
            } else {
                fnList.forEach(fn => {
                    fn.apply(this, params.slice(1));
                });
            }
        }
        /**
         * 移除事件監聽
         * @param {事件類型} type
         * @param {回調方法} fn
         */
    off(type, fn) {
        if (!this.listenerList[type]) {
            return false;
        } else {
            let index = this.listenerList[type].findIndex(vv => vv === fn);
            if (index === -1) {
                return false;
            } else {
                this.listenerList[type].splice(index, 1);
            }
        }
    }
}

let eventBus = new EventEmiter();

function cb(param) {
    console.log("this is a test", param);
}

eventBus.on("test", cb);

eventBus.emit("test", 123);

eventBus.off("test", cb);

eventBus.emit("test", 456);

  以上只是對發佈訂閱模式進行一個簡單的實現,自定義事件只能分發給在觸發前已訂閱的消息,針對那些先觸發,後訂閱的內容,並不能獲得一個很好的處理,因此,若是要解決這種弊端,就必須加一個離線的事件緩存。除此以外,發佈訂閱也有一些弊端,那就是每次發佈消息,都會觸發全部的事件監聽回調,儘管大多數狀況下並不想觸發全部回調內容,因此在這種狀況下,最好對事件加一些命名空間,以縮小其生效範圍。框架

  如下爲支持離線事件代碼,只是對事件加了一個標記:異步

class EventEmitter {
    
    constructor() {
        //回調中心
        this.listenerMap = {};
        //離線事件列表
        this.offlineListenerList = [];
    }
    /**
     * 添加事件監聽
     * @param type 事件類型
     * @param fn 回調函數
     * @param flag 是不是離線事件
     */
    on(type, fn, flag) {
        if (!this.listenerMap[type]) {
            this.listenerMap[type] = [];
        }
        this.listenerMap[type].push(fn);
        //若是註冊了離線事件,則在監聽事件時,須要檢測是否有離線事件緩存
        if (flag) {
            let index = this.offlineListenerList.findIndex(vv => vv.type === type);
            if (index !== -1) {
                fn.call(this, this.offlineListenerList[index].params);
                //清空該條離線事件記錄
                this.offlineListenerList.splice(index, 1);
            }
        }
    }
    /**
     * 觸發事件監聽
     * @param type 事件類型
     * @param params 載荷參數
     * @param flag
     */
    emit(type, params, flag) {
        let fnList = this.listenerMap[type];
        if (fnList && Array.isArray(fnList)) {
            fnList.forEach(fn => {
                fn.apply(this, params);
            });
        }
        //若是註冊的是離線事件,則吧
        if (flag) {
            this.offlineListenerList.push({
                type,
                params
            });
        }
    }
    /**
     * 移除事件監聽
     */
    off(type, fn) {
        if (!this.listenerMap[type]) {
            return false;
        } else {
            let index = this.listenerMap[type].findIndex(vv => vv === fn);
            if (index === -1) {
                return false;
            } else {
                this.listenerMap[type].splice(index, 1);
            }
        }
    }
    
    /**
     * 只觸發一次
     * @param type
     * @param fn
     */
    once(type, fn) {
        let fnList = this.listenerMap[type];
        let params = Array.from(arguments);
        if (!fnList || fnList.length === 0) {
            return false;
        } else {
            let index = fnList.findIndex( vv => vv === fn);
            fnList[index].apply(this, params.slice(1));
            fnList.splice(index, 1);
        }
    }
}

let event = new EventEmitter();

event.emit('test', 1, true);

event.on('test', params => {
   console.log('offline', params)
}, true);

event.on('cc', () => {
    console.log('normal', 22222);
});

event.emit('cc');
相關文章
相關標籤/搜索