javascript设计模式之发布订阅模式

  发布-订阅设计模式对大家来说并不是很陌生,举一个最简单的例子,在前端开发过程中,事件的绑定就是其实际的应用。首先我们先了解下什么是发布-订阅模式。

    基本概念:发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知。在前端开发中,我们一般用事件模型来替代传统的发布-订阅模式。

  发布-订阅模式是前端常用的一种设计模式,现在主流的MVVM框架,都大量使用了此设计模式,其主要作用有以下两点:一是可以实现模块间通信,而是可以在一定程度上实现异步编程。其基本事件模型如下:

  前端的事件绑定有三要素,一是传入事件类型,二是声明对应的回调方法,三是触发条件;触发条件为对应的事件类型。前端DOM的事件系统本质也是发布-订阅模式,而我们在业务处理中所应有的模式也与此类似,只不过发布订阅模式应用的是自定义事件类型,可以自定义。

  发布订阅事件模型本质上就是一个中央事件处理总线,它接收所有订阅的自定义事件,并将自定义事件的回调存储到事件回调的堆栈中,当某在某个时刻触发了此自定义事件,会调用分发事件的方法,从事件回调堆栈中取出符合条件的事件回调,依次调用,具体实现逻辑如下:

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');

 

  

posted @ 2019-01-08 21:16  Gerryli  阅读(932)  评论(0编辑  收藏  举报