前端设计模式——发布-订阅模式(观察者模式)

107 阅读2分钟

发布-订阅模式 又叫做观察者模式或消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合

举例

<button id='btn'>点击后弹出 alert 弹框</button>	<!-- 用户点击时相当于发布者发布消息 -->
  <script>
    btn.addEventListener('click', () => alert('用户点击了按钮!!'));  // 订阅者订阅了 btn 的click 时间
  </script>

发布者 定义了 addEventListener 里的事件属性和回调函数,
订阅者 订阅了事件属性,当发布者里有对应事件type,订阅者触发时,会调用所需要的方法

代码说明

  • 简易

模拟事件,拥有发布,订阅方法

class PublishSubscribeBasic {
        subscribes = {}; // 保存所有的订阅事件

        /**
         * 订阅函数
         * @param {*} subscribe 订阅的事件名
         * @param {*} callback 订阅的回调函数
         */
        on(subscribe, callback) {
            const subscribeCallbacks = this.subscribes[subscribe] || [];
            this.subscribes[subscribe] = [...subscribeCallbacks, callback];
        };

        /**
         * 发布函数
         * @param {*} subscribe 订阅的事件名
         * @param  {...any} args 发布时传递的参数
         */
        emit(subscribe, ...args) {
            const subscribeCallbacks = this.subscribes[subscribe] || [];
            subscribeCallbacks.forEach(callback => callback.call(this, args));
        };
    };

    const pubsub = new PublishSubscribeBasic();
    pubsub.on('A', (args) => console.log(args));	// 订阅一个 A 事件并传递回调函数
    setTimeout(() => pubsub.emit('A', '这是 A 收到的第一个参数'), 1000);	// 一秒后发布 A 事件并传递参数
  • 完善版本

模拟事件,拥有发布,订阅,取消订阅方法

class EevebtBus { 
        eventMap = new Map(); // 保存所有的订阅事件

        /**
         * 模拟 UUID,用于某订阅事件回调函数对应的 key
         * @returns 模拟获取一个 UUID 
         */
        getUUID() {
            return String(Date.now()) + String(Math.random());
        };
        /**
         * 获取事件名对应的所有订阅事件,如果没有,返回一个空 Map
         * @param {*} subscribe 
         * @returns 事件名对应的所有订阅事件的回调函数
         */
        getSubscribe(subscribe){
            return this.eventMap.get(subscribe) || new Map()
        } 
        /**
         * 订阅事件
         * key 事件名称
         * callback 回调函数
         *  @returns 本次订阅的标识符,用于之后取消订阅
         * */
        on(key,callback) {
            const uid = this.getUUID();
            let nowobj = this.getSubscribe(key);//初始化
            nowobj.set(uid,callback);
            !this.eventMap.get(key) && this.eventMap.set(key,nowobj);
            return uid;
        }
        /**
         * 发布事件
         * key 事件名称
         * args 入参
         * */
        emit(key,...args){
            let nowobj = this.getSubscribe(key);//初始化
            for (const [uuid, callback] of nowobj) {
                callback.call(this, args);
            };
        }
        /**
         * 取消订阅
         * key 事件名称
         * 可以是事件名,此时会取消事件名对应的所有订阅;可以为某个订阅的 uuid,此时仅取消此个订阅
         * */
        remove(key){
            const isSubscribe = typeof key === 'string' && this.eventMap.get(key);  // 是事件名
            const isUUID = !isSubscribe && typeof key === 'string';
            if(isSubscribe){
                this.eventMap.delete(key)
            }else if(isUUID){
                for (const [subscribe, subscribeCallbacks] of this.eventMap) {
                    for (const [uuid, callback] of subscribeCallbacks) {
                    if (uuid === key) {
                        subscribeCallbacks.delete(key);
                        return;
                    };
                    };
                };
            }
            return;
        }
    }
    const pubsub = new EevebtBus();
    const A =  pubsub.on('A', (args) => console.log(args));	// 订阅一个 A 事件并传递回调函数
    const B = pubsub.on('B', (args) => console.log(args));	// 订阅一个 A 事件并传递回调函数
    // setTimeout(() =>pubsub.remove(A), 1000);	// 销毁事件
    setTimeout(() =>pubsub.remove('A'), 1000);	// 销毁事件
    setTimeout(() =>{
        pubsub.emit('A', '这是 A 收到的第一个参数')
        pubsub.emit('B', '这是 B 收到的第一个参数')
    }, 1000);	// 一秒后发布 A 事件并传递参数