面试官必问系列:手写发布-订阅模式和观察者模式

2023-05-16

目录

前言

1、发布-订阅模式

定义

手写发布-订阅模式

2、观察者模式

定义

手写观察者模式

3、二者的根本区别


前言

发布-订阅者模式和观察者模式是两种设计模式,本质相同,但是也有区别,在Vue中有不同的应用场景。二ue响应式数据的基础,有关数据代理与数据劫持的知识点,可以参考这篇文章同学刷抖音的间隙,我学会了Vue数据代理与数据劫持的原理_czjl6886的博客-CSDN博客

1、发布-订阅模式

定义

存在一个“信号中心”,某个任务完成,就向信号中心,发布(publish)一个信号,其他任务向信息中心订阅(subscribe)这个信号,从而知道自己什么时候可以开始执行,即:发布-订阅模式

手写发布-订阅模式

伪代码中:emit触发事件,发布数据,on订阅这个事件,接收该数据 ,即:on-订阅, off取消订阅, emit发布

<script>
    class EventEmitter{
      constructor(){
        // 初始化为一个空的新对象
        // 在vm上创建一个_events对象,用来存放事件
        this._events=Object.create(null)
      }
      // 监听事件,获得emit触发事件后提供的数据
      on(event,fn){
        // 如果是数组,则循环监听数组事件
        if(Array.isArray(event)){
          event.forEach(item => {
            this.on(item,fn)
          })
        }else{
          (this._events[event] || (this._events[event]=[])).push(fn)
        }
      }
      // 关闭事件绑定
      off(event,fn){
        // 如果参数为空,则关闭所有的事件绑定
        if(!arguments.length){
          // 重新设置为一个空对象
          this._events = Object.create(null)
          return
        }
        // 如果传进来的事件没有emit,则返回;emit用于触发事件
        if(!this._events[event]) return
        // 如果没有传入回调函数,则默认移除该事件绑定的所有回调函数,
        // 否则移除与fn相同的回调
        if(!fn){
          this._events[event] = []
          return
        }else{
          // this._events[event]是引用数据类型,与cbs指向同一个数据
          const cbs = this._events[event]
          let i = this._events[event].length
          while(i--){
            if(cbs[i] === fn || cbs[i].fn === fn){
              cbs.splice(i,1)
              continue
            }
          }
        }
      }
      // 只触发一次自定义事件
      once(event,fn){
        const on=()=>{
          // 在第一次执行的时候将该事件销毁
          this.off(event,on)
          fn.apply(this,arguments)
        }
        on.fn = fn
        this.on(event,on)
      }
      // 触发事件,提供数据
      emit(event,...args){
        const cbs=this._events[event]
        if(cbs){
          cbs.forEach(fn => {
            fn.apply(this,args)
          })
        }
      }
    }
    const initEvent = new EventEmitter()
    function fnTest(value){
      console.log("This is a event" + value)
    }
    // 绑定单击事件click与回调函数fnTest
    initEvent.on("click",fnTest)

    initEvent.once("click",fnTest)

    initEvent.emit("click","这个参数指的是emit触发的事件要传递的数据")
    // 关闭事件绑定
    initEvent.off("click")

  </script>

2、观察者模式

定义

当事件发生时,观察者(订阅者,watcher)执行update()方法,完成相应的操作;被观察的目标(发布者,Dep)使用数组subs来存储所有的观察者,也可以使用addSub()添加新的观察者,当有事件发生时,调用所有观察者的update()方法。

手写观察者模式

<script>
    // 被观察的目标,即发布者:Dep
    class Dep{
      constructor(){
        // 记录所有的观察者,即订阅者
        this.subs = []
      }
      // 添加新的观察者
      addSub(sub){
        // 该订阅者存在且有update方法,就将其添加到subs数组中
        if(sub && sub.update){
          this.subs.push(sub)
        }
      }
      // 移除观察者
      removeSub(sub) {
        if (this.subs.length) {
          let index = this.indexOf(sub)
          if (index > -1) {
            this.sub.splice(index, 1)
          }
        }
      }
      // 发布更新通知
      notify(){
        this.subs.forEach(item =>{
          item.update()
        })
      }
    }

    // 观察者,即订阅者
    class Watcher{
      update(){
        console.log("****更新相关数据****")
      }
    }

    let dep = new Dep
    let watcher1 = new Watcher
    let watcher2 = new Watcher

    // 添加新的观察者
    dep.addSub(watcher1)
    dep.addSub(watcher2)
    dep.removeSub(watcher2)
    // 发布
    dep.notify()

  </script>

3、二者的根本区别

发布-订阅模式:由一个统一调度中心调用,发布者和订阅者之间不需要知道对方的存在;

观察者模式:由具体的目标调度,如,某一个事件被触发时,Dep就会调用数组中保存的和这个事件有关系的观察者的方法,因此,观察者模式中的订阅者与发布者之间存在依赖关系;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

面试官必问系列:手写发布-订阅模式和观察者模式 的相关文章

随机推荐