Vue源码研究02-依赖收集-数据变化后应该通知依赖这个数据的DOM

322 阅读2分钟

Vue源码研究02-依赖收集-数据变化后应该通知依赖这个数据的DOM

2.1 什么是依赖收集?

当定义在data中的一个数据发生变化时,应该通知页面中的那些DOM更新显示呢?如果更新整个页面的话,重绘、重排、页面效率低,显示不合理。

对,谁用到了这个数据(换句话,==谁依赖了这个数据==),就通知谁变化。每个数据对应一个==依赖数组==,谁依赖了这个数据,就把谁放到这个数据对应的依赖数组中。当这个数据变化时,就把这个数据对应的依赖数组中的依赖,挨个通知下,让他们重新渲染下视图。这个==过程==就是依赖收集。

2.2 什么时候收集依赖?什么时候通知依赖更新?

读数据的时候,收集依赖;写数据的时候,通知依赖更新。

读数据的时候会触发getter方法;写数据的时候会触发setter方法。

因此,在getter方法中收集依赖,在setter方法中通知依赖更新。

2.3 依赖是什么?

谁用到了这个数据谁就是依赖,在Vue中定义了一个==Watcher类==来描述这个依赖,一个依赖就是一个Watcher实例。一个Watcher实例,是一个观察者,是一个哨兵。可以思考下这里是设计模式中的那种模式呢?是观察者模式还是发布订阅模式?Vue源码的设计中用到了设计模式和面向对象思想。

实例的代码描述如下:

class Watcher {
  constructor (vm, expOrFn, cb, options) {
    this.cb = cb; // 回调
    this.vm = vm; // 当前Vue实例
    this.getter = parsePath(expOrFn)
    this.value = this.get() // 获取下被依赖的数据,目的是触发数据的get方法
  }

  get () {
    window.target = this; // 将当前Watcher赋给全局的唯一对象
    const vm = this.vm
    let value = this.getter.call(vm, vm) // 获取被依赖的数据
    window.target = undefined; // 释放 window.target
    return value
  }

  update () { // 更新视图的方法
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}

/**
 * Parse simple path.
 * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
 * 例如:
 * data = {a:{b:{c:2}}}
 * parsePath('a.b.c')(data)  // 2
 */
const bailRE = /[^\w.$]/
export function parsePath (path) {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}

实例化Watcher会执行其构造方法。在构造方法中会调用this.get()实例方法,将当前实例==挂载到window.target上==,并获取下被依赖的数据,目的是==触发数据的getter方法==,getter方法中会调用dep.depend()收集依赖,而在dep.depend中会==获取挂载到window.target==上的Watcher存入到依赖数组中,最后释放掉window.target。

2.4 依赖管理器

2.1节中,我们说每个数据对应一个依赖数组,谁依赖了这个数据,我们就把谁(Watcher)放入到依赖数组中。不过,只用一个数组去存放管理依赖,功能很难满足我们场景的需求。因此,我们定义一个类,好像一个依赖管理器,存放和管理数据对应的依赖。

class Dep { // 一个依赖收集类,一个依赖的集合
  constructor () {
    this.subs = [] // 用来存放Watcher对象的数组
  }

  addSub (sub) { // 在subs中添加一个Watcher对象
    this.subs.push(sub)
  }

  removeSub (sub) { // 从subs中删除一个Watcher对象
    remove(this.subs, sub)
  }

  notify () { // 通知所有Watcher对象更新视图
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].updte()
    }
  }
}

数据变化时,会触发setter方法,在setter中调用实例的notify方法,在notify方法中会遍历依赖中的所有watcher实例,并调用watcher实例的update方法更新视图。

2.5 图解依赖收集和视图更新的过程

image-20200811081044166