带你轻松搞定“观察者模式“和"发布订阅模式"的区别!

3,108 阅读2分钟

模式的概念

最近我的小伙伴们在面试中,这两个被问的很频繁,斗胆的跟大家分享一下我的理解。我将采用“官方解释”+“大白话”的方式。

熟悉Vue的小伙伴都知道,在Vue中使用“观察者模式”去通知视图更新,使用“发布订阅”$on,$emit来实现自定义事件,所以搞清楚两者之间的关系很重要啦!

观察者模式:一个对象(观察者)订阅另一个对象(主题),当主题被激活的时候,触发观察者里面的事件。

大白话解释:当去你去医院打吊瓶(某些地方叫打点滴,就是这么严谨)的时候,医生要观察吊瓶的改变,当快打完的时候,就要通知医生来取针。这里医生就是观察者(Obeserver),吊瓶就是被观察者/主题。

     class Subject { //被观察者数据---吊瓶
           constructor(name="知了哥"){
               this.state = 100;
               this.name = name;
               this.obs = [] //会有多个观察者
           }
          addObs(ob){
               this.obs.push(ob)
          }
          setState(state){ //改变状态的方法
              this.state = state
              //要去通知观察者去做动作,--- 拔针 
              this.obs.forEach((ob)=>{
                   ob.update(this) //让观察者去做更新
              })
          }
     }

     class Obeserver{ //观察者 -- 医生
         
         constructor(name){
             this.name = name;
             //在观察者里面是不是也可以记录 观察了哪些 数据(吊瓶)
         }
         update(subject){
               //医生也有可能观察多个 打针的人 
               if (!subject.state) {
                     console.log(`${this.name} 收到通知 :${subject.name} 的 带瓶打完啦!`)
               }else {
                    console.log(` ${this.name} 收到通知 :${subject.name} 的 带瓶量: ${subject.state}!`)
               }
        
         }

     }
     var zhiliao = new Subject();
     var hushi = new Obeserver("护士");
     var yisheng = new Obeserver("医生")
     zhiliao.addObs(hushi) //把观察者放到被观察这的里面去
     zhiliao.addObs(yisheng)
     zhiliao.setState(50) 

发布订阅:订阅者把自己想要订阅的事件注册到调度中心,当发布者发布事件到调度中心(就是该事件被触发),再由调度中心统一调度订阅者注册到调度中心的处理代码。

大白话解释:其实简单理解就是我们的自定义事件,比如在Vue中,Vue实例($bus)就是统一的调用中心,我们使用$bus$on一个自定义事件myEvent就是发布者发布一个事件到调度中心,然后在其他地方$bus.$emit(myEvent)一下就相当于事件被触发,然后$bus就去执行对应事件的回调函数 。哈哈,好绕口!再比如毛十八去邮局里面订阅报纸,订阅了南方都市报,那南方都市报就是我们的发布者,当发布新报纸的时候,邮局再去通知所有的订阅者取报纸啊!

    
var Event = {
    _listeners: {},    
    // 添加
    $on: function(type, fn) {
        if (typeof this._listeners[type] === "undefined") {
            this._listeners[type] = [];
        }
        if (typeof fn === "function") {
            this._listeners[type].push(fn);
        }    
        return this;
    },
    // 触发
    $emit: function(type) {
        var arrayEvent = this._listeners[type];
        if (arrayEvent instanceof Array) {
            for (var i=0, length=arrayEvent.length; i<length; i+=1) {
                if (typeof arrayEvent[i] === "function") {
                    arrayEvent[i]({ type: type });    
                }
            }
        }    
        return this;
    },
    // 删除
    $off: function(type, fn) {
        var arrayEvent = this._listeners[type];
        if (typeof type === "string" && arrayEvent instanceof Array) {
            if (typeof fn === "function") {
                for (var i=0, length=arrayEvent.length; i<length; i+=1){
                    if (arrayEvent[i] === fn){
                        this._listeners[type].splice(i, 1);
                        break;
                    }
                }
            } else {
                delete this._listeners[type];
            }
        }
        return this;
    }
};
    
   

总结:学习这两种模式,其实就是为了 代码解耦 ,让每个独立的对象分开。小伙伴需要从日常的业务场景去观察,比如在分页插件中,就可以利用“发布订阅”为点击每一个分页数的时候触发($emit)一个自定义事件,如果需要在点击分页的是做一些其他的时候,就可以在外面$on 这个事件,那么分页插件就可以很好的封装起来,不用给里面传递参数或者回调函数之类的了。