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

      

  • 相关阅读:
    log4net 配置 一站式解决
    设计模式-职责链模式(ChainOfResponsibility)
    springboot+Kafka(生产者和消费者)
    springboot启动过程分析
    Eureka注册中心原理
    JDK8常量池整理
    第2章 Java内存区域与内存溢出异常
    第5章 数据库分库分表实例
    物理分页和内存分页-引用
    spring4体系架构
  • 原文地址:https://www.cnblogs.com/gerry2019/p/10241488.html
Copyright © 2020-2023  润新知