学不会来打我-观察者模式

171 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

今天我来讲一下咱们的设计模式中的观察者模式哈,或者说是订阅-发布模式

这次的课程分三个部分哈,首先第一部分我们先讲述一下观察者模式定义,第二部分呢我们会讲述一下观察者模式js中的应用,第三部分呢我们使用原生js实现一个观察者模式

image.png

先抛概念

观察者模式(Observer)通常又被称为 发布-订阅者模式消息机制,它定义了对象间的一种一对多依赖关系,只要当一个对象状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题。

image_1.png

image_2.png

我们举个例子

我要点外卖,但是外卖太火关店了,得客流量下来才能开店,但是我又不知道什么时候开店,所以我要使用一个功能,我们就称之为订购哈,我先选好了我要什么,送到哪里,然后让外卖开店的时候给我自动下单

这样呢 外卖店也不怕因为临时关店丢到了订单,我也不会因为无法下单而吃不上饭

image_3.png

这就是观察者模式

观察者模式在js中的应用

最典型的就是我们每个人都使用过的dom监听,比如下面的代码

document.querySelector('.btn').addEventListener('click',function () {
    alert('You click this btn');
},false)

没错,我们平时对 DOM 的事件绑定就是一个非常典型的 发布-订阅者模式,这里我们需要监听用户点击按钮这个动作,但是我们却无法知道用户什么时候去点击,所以我们订阅 按钮上的 click 事件,只要按钮被点击时,那么按钮就会向订阅者发布这个消息,我们就可以做对应的操作了。

除了我们常见的 DOM 事件绑定外,观察者模式应用的范围还有很多~

比如比较当下热门 vue 框架,里面不少地方都涉及到了观察者模式,比如数据和视图的绑定

2311632628707_.pic_hd.jpg

大致的实现是设置一个监听器 Observer,用来监听所有属性,会给每个订阅者Watcher绑定上指令解析器 Compile 解析对应的指令生成的update方法。如果属性上发上变化了,就需要告诉订阅者 Watcher 去执行update方法更新视图。

还有就是子组件与父组件通信

Vue 中我们通过 props 完成父组件向子组件传递数据,子组件与父组件通信我们通过自定义事件即 $on,$emit来实现,其实也就是通过 $emit 来发布消息,并对订阅者 $on 做统一处理。

ok,说了这么多,该我们自己露一手了,接下来我们来自己创建一个简单的观察者。

首先我们创建一个观察者对象,这里为了好理解我们使用订阅者和发布者来进行下面的讲述,来对应观察者和被观察者

const Observe = {}

这个发布者对象至少可以订阅是吧,要告诉这个对象当外卖店开门了要给我下订单对吧。我们称这个方法为on

const Observe = {
  on(type, fn) {} 
}

他接收两个参数,一个是事件类型,也就是开店或者关店什么什么的,另一个就是当事件发生时的回调。也就是开店了之后我要做什么,这里我们就发现了,我们可以订阅很多的这个事件,比如开店了我点外卖,那还有别人点奶茶什么的。当开店发生的时候,这些绑定的回调都是需要执行的,所以这里需要一个存储回调队列。我们称之为queue

const Observe = {
  queue: {},
  on(type, fn) {
    if (typeof this.queue[type] === 'undefined') {
      // 将执行方法推入该事件对应的执行队列中
      this.queue[type] = [fn];
    }
    else {
      // 如果此消息存在,直接将执行方法推入该事件对应的执行队列中
      this.queue[type].push(fn);
    }
  } 
}

这里我们每一种事件的类型,都绑定了一个回调的数组。当事件发生的时候我们应该挨个执行这些回调是吧,外卖开店了赶紧给我定外卖,赶紧给他定奶茶。这个方法我们叫做trigger,也就是所谓触发这个事件。

const Observe = {
  queue: {},
  on(type, fn) {
    if (typeof this.queue[type] === 'undefined') {
      // 将执行方法推入该事件对应的执行队列中
      this.queue[type] = [fn];
    }
    else {
      // 如果此消息存在,直接将执行方法推入该事件对应的执行队列中
      this.queue[type].push(fn);
    }
  },
  trigger(type) {
    if (!this.queue[type]) {
      return;
    }
    this.queue[type].forEach(fn => {
      fn();
    })
  } 
}

这里我们的观察者其实已经可用了。我们实验一下:

image_4.png

看!我们已经成功的实现了我们的观察者,为自己点个赞吧!

留个作业,我们这里完成了一个最简单的观察者,但是这里有两个问题:

  1. 只有订阅没有取消订阅,取消订阅的方法怎么写呢?

  2. 当我们使用Observe.queue的时候可以获取到这个事件的队列并且随意修改,这样显然是不安全的,请问怎样能让外部访问不了queue队列呢?

  3. 本节课中我们为了讲述原理,将观察者模式订阅-发布模式放到一起讲了,但是实际上两者是有一定的差距的,大家怎么理解这个差距的?