前言

在面试题中常常会呈现与“公布订阅”模式相干的题目,比方考查咱们对Vue响应式的了解,也会有题目间接考验咱们对“公布订阅”模式或者观察者模式的了解,甚至还会有一些手写算法题。笔者就在往年三月加入某平安公司的面试时被要求手写代码实现“公布订阅”模式,过后因为没有筹备没有答复上来,悔不该当初。由此可见“公布订阅”模式是一个十分重要的设计模式,接下来咱们一起学习下吧。

观察者模式 vs “公布订阅”模式

首先须要廓清的是,这两者尽管类似,却有不同。

观察者模式

只波及两个要害角色,发布者与订阅者。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个指标对象,当这个指标对象的状态发生变化时,会告诉所有观察者对象,使它们可能自动更新。

发布者的行为:

  1. 减少订阅者
  2. 移除订阅者
  3. 告诉所有订阅者

代码实现如下:

 // 定义发布者类 class Publisher {  constructor() {    // 创立订阅者    this.observers = []  }  // 减少订阅者  add(observer) {    this.observers.push(observer)  }  // 移除订阅者  remove(observer) {    this.observers.map((item, i) => {      if (item === observer) {        this.observers.splice(i, 1)      }    })  }  // 告诉所有订阅者  notify() {    this.observers.map((observer) => {      observer.update(this)    })  }}

订阅者行为:

  1. 被告诉
  2. 去执行

代码实现:

 // 定义订阅者类   class Observer {    constructor() {        console.log('创立订阅者')    }    update() {        console.log('订阅者更新')    }}

“公布订阅”模式

与观察者模式相似,然而在这种模式下发布者齐全不必感知订阅者,不必关怀它怎么实现回调办法,事件的注册和触发都产生在独立于单方的第三方平台(事件总线)上。公布-订阅模式下,实现了齐全地解耦。

vue2.x响应式原理探索

响应式实现过程官网解释

  1. 在Vue data中存入对象数据
  2. Vue遍历此对象每个property,通过Object.defineProperty()办法进行数据劫持,给每一个property加上getter/setter
  3. 每个组件实例都对应一个Watcher实例,会收集所有接触过的property依赖,如果数据有变动,将会收到告诉,watcher会使其关联的组件实例扭转

深刻了解响应式原理 官网文档v2

公布订阅模式在vue响应式原理中的具体使用

初始化过程

class Vue {    constructor(options) {      this.$options = options;      this.$data = options.data;              // 对data选项做响应式解决      observe(this.$data);              // 代理data到vm上      proxy(this);              // 执行编译      new Compile(options.el, this);    }  }  

发布者/伪订阅者:Observe

observe在前文中代表了订阅者,但这里他更多的是一个发布者,执行了发布者的权力,减少了订阅者,并且在扭转时告诉了订阅者。

function observe(obj) {    if (typeof obj !== "object" || obj == null) {      return;    }    new Observer(obj);  }    class Observer {    constructor(value) {      this.value = value;    Object.keys(value).forEach((key) => {        defineReactive(value, key, value[key]);      });   }} 

defineReactive是一个十分重要的办法,为每⼀个key创立⼀个Dep实例

function defineReactive(obj, key, val) {    // 递归遍历 确保深层次key也可能响应  this.observe(val);    // 对每个key都建设一个Dep管家  const dep = new Dep();    // 应用defineProperty对每个key建设getter/setter  Object.defineProperty(obj, key, {      get() {        Dep.target && dep.addDep(Dep.target);// Dep.target也就是Watcher实例        return val;      },      // 监听变动    set(newVal) {        if (newVal === val) return;        // 告诉dep执行更新办法       dep.notify();     },    });  } 

事件核心:Dep管家,治理实在订阅者Wacther

Dep收集了组件实例中同一个key对应的所有订阅者Wacther

// 发布者class Dep {    constructor() {      this.deps = [];  // 依赖治理    }    addDep(dep) {      this.deps.push(dep);    }    notify() {       // 实际上是调用的watcher中的更新事件    this.deps.forEach((dep) => dep.update());    }  }  

理论的订阅者:Watcher

通过触发get,将watcher增加到key对应的Dep中

// 订阅者 负责更新视图  class Watcher {    constructor(vm, key, updater) {      this.vm = vm      this.key = key      this.updaterFn = updater      // 创立实例时,把以后实例指定到Dep.target动态属性上      Dep.target = this      // 读一下key,触发get 便将watcher增加到key对应的Dep中      vm[key]      // 置空      Dep.target = null    }      // 将来执行dom更新函数,由dep调用的    update() {      this.updaterFn.call(this.vm, this.vm[this.key])    }  }  

VUE2.X响应式的局限性

因为js的限度,Vue 不能检测数组和对象的变动。尽管如此咱们还是有一些方法来回避这些限度并保障它们的响应性。

对象

Vue 无奈检测 property 的增加或移除,因为Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以只有在初始化实例时就存在于data的property才是响应式的。

对于曾经创立的实例,Vue 不容许动静增加根级别的响应式 property。然而,能够应用 Vue.set(object, propertyName, value) 办法或者其别名vm.$set向嵌套对象增加响应式 property。例如:

Vue.set(vm.someObject, 'b', 2)this.$set(this.someObject,'b',2)

为已有对象赋值多个新 property,需用原对象与要混合进去的对象的 property 一起创立一个新的对象。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

数组

Vue 不能检测以下数组的变动:

  1. 当你利用索引间接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你批改数组的长度时,例如:vm.items.length = newLength

为了解决第一类问题,以下两种形式都能够实现和 vm.items[indexOfItem] = newValue 雷同的成果,同时也将在响应式零碎内触发状态更新:

// Vue.setVue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splicevm.items.splice(indexOfItem, 1, newValue)

你也能够应用 vm.$set 实例办法,该办法是全局办法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你能够应用 splice

vm.items.splice(newLength)

感激

本篇文章探讨了公布订阅模式以及观察者模式,同时也探讨了公布订阅模式在vue2.x响应式中的利用,如有不对,欢送斧正,下一篇文章我将持续探索vue3.x的响应式原理,如果感觉写的还行就帮我点个赞吧,这样我会更有能源进行接下来的常识输入!谢谢各位朋友的观看!