Zerlinda's Blog

JavaScript的观察者模式

一、前言

1、定义

       观察者模式(发布-订阅模式):其定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。 

2. 解决的问题

  好莱坞有句名言. “不要给我打电话, 我会给你打电话”。 这句话就解释了一个观察者模式的来龙去脉。 其中“我”是发布者, “你”是订阅者。

       再举个例子,我来公司面试的时候,完事之后每个面试官都会对我说:“请留下你的联系方式, 有消息我们会通知你”。 在这里“我”是订阅者, 面试官是发布者。所以我不用每天或者每小时都去询问面试结果, 通讯的主动权掌握在了面试官手上。而我只需要提供一个联系方式。

       因此,观察者模式可以很好的实现2个模块之间的解耦。

3. 模式中的角色

  3.1 抽象主题(Subject):定义方法,提供一个接口,可以增加和删除观察者对象。

  3.2 具体主题(ConcreteSubject):相当于在上例中的面试官,在具体主题内部状态改变时,给所有登记过的观察者发出通知。

  3.3 抽象观察者(Observer):定义方法,为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

       3.4 具体观察者(ConcreteObserver):相当于在上例中的面试者,存储有关状态,这些状态应该与 ConcreteSubject 的状态保持一致。同时实现 Observer 的更新接口以使自身状态与 ConcreteSubject 状态保持一致(执行回调)。

       主题和观察者定义了一对多的关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。关于观察者的一切,主题只知道观察者实现了某个接口。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节,将对象之间的相互依赖性降到最低。符合了设计原则:为了交互对象之间的松耦合设计和努力。

二、JavaScript中的观察者模式

       以上大部分是Java中的设计模式概念,在JavaScript中,观察者模式是一种创建松散耦合代码的技术。一般使用事件模型来替代传统的观察者模式,同样也是‘发布-订阅’的一种形式。

好处: 

(1)可广泛应用于异步编程中,是一种替代传递回调函数的方案。 

(2)可取代对象之间硬编码的通知机制,一个对象不用再显示地调用另外一个对象的某个接口。两对象轻松解耦。

DOM事件是一个典型的观察者模式:

// 添加两个订阅者并且注册事件的回调
document.body.addEventListener("click", function() { 
console.log(1); 
}, false); 
document.body.addEventListener("click", function() { 
console.log(2); 
}, false); 
doucment.body.click();  

上面的代码我们订阅document.body上的click事件,当body节点被点击时,body节点便向订阅者发布这个消息。

下面是典型的js观察者模式,也是Vue的Event模块的核心,实现了发布和订阅的基本功能:

var Event = {
  //存储事件及回调
  _event: {},
  // 订阅事件,如果事件被触发,则执行callback回调函数
  on: function(eventName, callback) {   
    //eventName: array|string   callback: function
    var self = this;
    //递归数组
    if(Array.isArray(eventName)) {     
      for(var i = 0, len = eventName.length; i < len; i++) {
        this.on(eventName[i], callback);
      }
      return self;
    }
    (this._event[eventName] || (this._event[eventName] = [])).push(callback);
    return self;
  },
  // 触发事件 eventName
  emit: function(eventName) {         // eventName: string
    var self = this,
       args = [].slice.call(arguments, 1);
    if(self._event[eventName]) {
      for(var i = 0, len = this._event[eventName].length; i < len; i++) {
        this._event[eventName][i].apply(self, args)
      }
    }
  },
};
//订阅了test,node事件,绑定回调,同一事件可以绑定多个回调
Event.on(["test", "node"], function(result) {       
console.log(result);
});
Event.on('test', function() {                  
  console.log('test');
});
//触发test事件,通知观察者更新,执行回调
Event.emit('test', 'hello world'); // 输出 'hello world' 和 'test'

Vue中的event bus

Vue中,Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡,使用 Vuex 可能是繁琐冗余的。利用vue的event模块,在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线:我们需要有一个 Vue 实例来充当通信媒介的作用,Vue 官方文档里将它叫做 event bus。

//bus.js

export default new Vue(); 

当我们需要不同组件(包括非父子组件)之间事件通信的时候,只需要对这个 event bus 使用 $emit 和 $on 就可以了。

//a.vue

import bus from "../bus"
  console.log("test", test)
  export default {
    created: function(){
      var self = this;
      bus.$on("edit", function(e){
        self.detail = e;
      })
    },
    data() {
      return {
        detail: "",
      }
    },
}

//b.vue

  import bus from "../bus"
    export default {
      methods: {
        get(){
          bus.$emit("edit", "要变啦")
        }
      },
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注