发布订阅模式与观察者模式

115 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

发布订阅模式

  • 发布订阅模式
    • 发布者(发布信息)
    • 订阅者(接收信息)
    • 消息中心(传递信息)

首先,我们要有一个消息中心,去发布我们的消息,假设要执行事件a,我们就在要执行的时候由发布者通过消息中心去触发事件a,订阅者向消息中心订阅事件a,当订阅者收到a事件的信号时, 就去执行指定的操作,这就是发布订阅模式(publish-subscribe pattern)

个人理解

  • 订阅者要先去订阅
  • 在消息中心 存储事件的键值对中存入对应的处理函数
  • 当发布者 发布消息后,便去挨个执行该事件对应的处理函数

Vue的自定义事件、兄弟组件通过EventBus传值 用的就是发布订阅模式

Vue-自定义事件

兄弟组件传值:

//main.js
//事件中心
Vue.property.$EventBus = new Vue()

//componentA.vue
//发布者
methods:{
  publishEvent(){
    //发布消息  两个参数,事件名 传递的参数
    this.$EventBus.$emit('add-todo',{text: this.newTodoText})
  }
}

//componentB.vue
//订阅者
created() {  
  //订阅消息   两个参数, 事件名  处理函数(发布者传入的参数会作为事件处理函数的实参)
  //最好传入具名函数,便于销毁时解绑事件
  this.$EventBus.$on('add-todo', par => {
    	//一些操作
  })
}

发布订阅模式

简单实现:

<script>
    //定义事件触发器
    class EventEmitter {
        constructor () {
            //subs是一个对象,用来存储事件名及其对应的处理函数
            // {'click': [fn1, fn2], 'change': [fn]}
            this.subs = Object.create(null)    //设置为null表示Object.create创建的对象没有原型属性
        }
        //注册事件
        $on (eventType, handler) {   //事件类型 事件处理函数
            //首先判断subs中是否存储该 事件类型
            this.subs[eventType] = this.subs[eventType] || []
            this.subs[eventType].push(handler)
        }

        //触发事件
        $emit (eventType) {
            if(this.subs[eventType]) {
                this.subs[eventType].forEach( handler => {
                    handler()
                })
            }
        }
        //只允许执行一次的事件
        $once(eventType, handler){
          this.subs[eventType] = this.subs[eventType] || []
          let onceFn = () => {
            handler()
            this.$off(eventType, onceFn)
          }
          this.$on(eventType, onceFn)
        }
        //解绑事件
        $off(eventType, handler){
          if(!this.subs.hasOwnProperty(eventType)){
            throw new Error('事件未注册')
          }else if(typeof handler !== 'function'){
            throw new Error('请传入一个函数')
          }else{
             this.subs[eventType].splice(this.subs[eventType].findIndex(item => item == handler),1)
          }
        }
    }

    let bus = new EventEmitter()

    bus.$on('click', () => {
        console.log('click1')
    })

    bus.$on('click', () => {
        console.log('click2')
    })

    bus.$emit('click')
</script>

观察者模式

  • 观察者(订阅者) -- Watcher
    • Update(): 当事件发生,具体要做的事情
    • 每个观察者都存在一个update方法,当事件发生时,会调用观察者的update方法
    • Vue响应式中,数据变化会调用 观察者的update方法, 用来更新视图
    • 订阅者的update方法是由发布者调用的
  • 目标(发布者)  -- Dep
    • subs数组: 存储所有的观察者,所有依赖该事件的观察者都会添加到数组中
    • addSub(): 添加观察者
    • Notify(): 当事件发生, 调用所有观察者的 update()方法

简单实现:

<script>
    //发布者 - 目标
    class Dep {
        constructor () {
            //记录所有订阅者(观察者)
            this.subs = []
        }
        // 添加订阅者
        addSub(sub) {
            if(sub && sub.update){   //判断sub是否为订阅者 - 即sub存在且有update方法
                this.subs.push(sub)
            }
        }
        // 发布通知
        notify() {
            this.subs.forEach(sub => {
                sub.update()
            })
        }
    }

    // 订阅者 - 观察者
    class Watcher {
        update() {
            console.log('update')
        }
    }

    //测试
    let dep = new Dep()
    let watcher = new Watcher()

    dep.addSub(watcher)
    dep.notify()
</script>

发布订阅模式与观察者模式区别

  • 观察者模式
    • 没有事件中心,只有发布者 和 订阅者, 且发布者需要知道订阅者的存在,所以发布者和订阅者之间存在依赖关系
    • 由具体目标调度,比如当事件触发,Dep就回去调用观察者的update方法
  • 发布订阅模式
    • 发布者和订阅者之间不需要知道对方的存在, 发布者与订阅者之间不存在依赖关系
    • 由统一的事件中心调度

yuque_diagram.jpg