订阅发布者模式

29 阅读3分钟

订阅发布者模式 subacribing/publisher 是一种开发模式。

优点:

 1.时间上的解耦
 2.对象之间的解耦

缺点: 发布订阅模式本身就消耗一定的时间和内存

这种模式由订阅者、处理中心、发布者三部分组成。订阅者只管在处理中心订阅,有人订阅它才响应。发布者触发时只通知处理中心,并不关心响应什么。

接下来实现一个订阅发布者的类

    
class Event {
    constructor() { }
    // 做一个事件集合
    handlers = {}
    // 事件添加器  也就是订阅者
    //接收两个参数 一个事件名称  一个事件方法    
    //一个名称下可以有多个方法
    addEventListener(type, handler) {
        //判断type是否已在handlers中存在
        if (!(type in handlers)) {
            //不存在 建立数组存储
            this.handlers[type] = []
        }
        this.handlers[type].push(handler)
    }
    //发布者 也就是响应订阅的方法
    // 两个参数 一个需要触发的事件名 一个响应方法的参数
    dispatchEvent(type, ...params) {
        if (!(type in handler)) {
            return new Error('未注册该事件')
        }
        this.handlers[type].forEach(ele => {
            ele(...params)
        });
    }
    // 取消订阅 也就是取消已经订阅的响应方法
    //两个参数 一个事件名  一个需要取消的方法
    removeEventListener(type, handler) {
        //先判断事件是否存在
        if (!(type in this.handlers)) {
            return new Error('无效事件')
        }
        // 先判断第二个参数传递了没 要是没有 就删除事件
        if (!handler) {
            delete this.handlers[type]
        } else {
            // 有的话就查找是哪个 找到索引然后删除
            const ids = this.handlers[type].findIndex(item => item === handler)
            //这一步就是校验为handler空的情况
            if (ids === undefined) {
                return new Error('无该绑定事件')
            }
            this.handlers[type].splice(ids,1)
             //删除完后查看是否为空 
            if( this.handlers[type].length==0){
                delete this.handlers[type]
            }
        }

    }

}

这就简单完成了订阅者、 发布者、 处理中心。
使用情况

  let event = new Event()
    function load (params) {
        console.log('load', params);
    }
    function load2 (params) {
        console.log('load2', params);
    }
    event.addEventListener('load', load)
    event.addEventListener('load', load2)

    event.dispatchEvent('load', 'load事件触发')
    // 删除全部load事件
    event.removeEventListener('load')

介绍一个简单的使用场景 在a模块中点击按钮 在b模块中显示实时增加的count变量

b模块订阅一个事件 给id为b的区域写入值为num a模块发布一个事件,每次点击按钮都激发 , 传入参数 ,b模块就能实施更新

   //a模块
   let count = 0
   //给按钮添加点击事件
    button.onclick = function () {
        event.dispatchEvent(addCount, ++count)
    }
     //b模块
    function add (num) {
        document.getElementById('b').innerHTML = num
    }
    event.addEventListener('addCount', add)
      

扩展

目前的模式都是先订阅后发布 假如先发布了就没有响应了。在使用场景中很可能就会用到先发布后订阅的情况,比如qq消息在离线后进行一个缓存聊天记录,登录的时候再把聊天记录从新渲染一遍,这时候就会用到,先发布,登录的时候再订阅的情况。我们可以对上面的Event类进行一个改动,增加缓存区。

主要改的地方是 订阅 和 发布的判断

class Event {
    constructor() { }
    handlers = {}
    //增加缓存区
    cache = {}
    addEventListener (type, handler) {

        //订阅的时候发现缓存区有这个事件
        //就触发 并且是缓存只触发一次 触发完就删掉该缓存
        if (type in this.cache) {
            //如果存在就触发 并把缓存的参数传入
            handler(this.cache[type])
            delete this.cache[type]
        }

        if (!(type in this.handlers)) {
            this.handlers[type] = []
        }
        this.handlers[type].push(handler)
    }
    dispatchEvent (type, ...params) {
        // 先发布 假如发布的时候没有type事件 
        // 那就把参数先存起来
        if (!(type in this.handlers)) {
            this.cache[type] = params
            return new Error('未注册该事件,存入缓存')
        }
        this.handlers[type].forEach(ele => {
            ele(...params)
        });
    }

    removeEventListener (type, handler) {
        if (!(type in this.handlers)) {
            return new Error('无效事件')
        }
        if (!handler) {
            delete this.handlers[type]
        } else {
            const ids = this.handlers[type].findIndex(item => item === handler)
            if (ids === undefined) {
                return new Error('无该绑定事件')
            }
            this.handlers[type].splice(ids, 1)
            if (this.handlers[type].length == 0) {
                delete this.handlers[type]
            }
        }
    }

}