Vue源码解读之依赖收集

原文地址:https://banggan.github.io/2019/01/19/Vue源码解读之依赖收集/
通过上一篇Vue源码解读之响应式原理,我们可以看到Vue利用defineReactive会把普通的对象装换为响应式对象,在defineReactive函数中,重点是const dep = new Dep(),在设置getter的时候,收集依赖也就是dep.depend()

依赖收集

先看一下getter做的事情:访问对象属性的时候触发

export function defineReactive (
 obj: Object,
 key: string,
 val: any,
 customSetter?: ?Function,
 shallow?: boolean
) {
 const dep = new Dep()//在每个响应式键值的闭包中定义一个dep对象
..........
 Object.defineProperty(obj, key, {
   enumerable: true,
   configurable: true,
   get: function reactiveGetter () {
   //拿到getter,没有getter就直接取val
     const value = getter ? getter.call(obj) : val
     //依赖收集的过程  Dep类:建立数据和watch之间的桥梁
     if (Dep.target) {
       dep.depend() // 进行收集依赖
       
       if (childOb) { 
         childOb.dep.depend()
         if (Array.isArray(value)) {
           dependArray(value)
         }
       }
     }
     return value  //最终返回value
   },
   // ...
 })
}

在getter 的时候进行依赖的收集,只有在 Dep.target 中有值的时候才会进行依赖收集;

这个 Dep.target 是在Watcher实例的 get 方法调用的时候 pushTarget 会把当前取值的watcher推入 Dep.target,原先的watcher压栈到 targetStack 栈中,当前取值的watcher取值结束后出栈并把原先的watcher值赋给 Dep.target,cleanupDeps 最后把新的 newDeps 里已经没有的watcher清空,以防止视图上已经不需要的无用watcher触发

Dep

Dep 是整个 getter 依赖收集的核心,主要是建立数据和watcher之间的桥梁,主要是记录哪些Watcher依赖自己的变化,dep.depend()作用是在调用Dep.target的addDep()函数。

let uid = 0  、、Dep的实例ID,为了方便去重

export default class Dep {
  static target: ?Watcher;// 当前是谁在进行依赖收集
  id: number;
  subs: Array<Watcher>; //所有的watcher集合

  constructor () {
    this.id = uid++   //Dep的实例id 
    this.subs = []    //存储收集器中需要通知的watcher
  }
  addSub(sub: Watcher) { ... }  //添加一个观察者对象
  removeSub(sub: Watcher) { ... }  //移除一个观察者对象
  depend () {//依赖收集,当存在Dep.target的时候把自己添加观察者的依赖中 
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  notify() { ... }  // 通知所有订阅者 
  }
}


Dep.target = null
const targetStack = []   // watcher栈
//将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈
export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}
//将观察者实例从target栈中取出并设置给Dep.target 
export function popTarget () {
  Dep.target = targetStack.pop()
}

Dep 是一个 Class,它定义了一些属性和方法,这里需要特别注意的是它有一个静态属性 target,这是一个全局唯一 Watcher,这是一个非常巧妙的设计,因为在同一时间只能有一个全局的 Watcher 被计算,另外它的自身属性 subs 也是 Watcher 的数组。它是Watcher的实例,用来通知更新。

watcher

再来看一下watcher的实现:

export default class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean    //是否是渲染watcher的标志位
  ) {

    this.getter = expOrFn    // 在get方法中执行
    if (this.computed) {    // 是否是 计算属性
      this.value = undefined
      this.dep = new Dep()//计算属性创建过程中并未求值
    } else {    // 不是计算属性会立刻求值
      this.value = this.get()
    }

  }

  /* 获得getter的值并且重新进行依赖收集 */

  get() {
    pushTarget(this)    // 设置Dep.target = this
    let value
    value = this.getter.call(vm, vm)
    popTarget() // 将观察者实例从target栈中取出并设置给Dep.target
    this.cleanupDeps()
    return value
  }
  addDep(dep: Dep)//添加一个依赖关系到Deps集合中 

  cleanupDeps() // 清理newDeps里没有的无用watcher依赖
  update()// 调度者接口,当依赖发生改变的时候进行回调 
  run() // 调度者工作接口,将被调度者回调 

  getAndInvoke(cb: Function) { ... }

  evaluate() //收集该watcher的所有deps依赖

  depend() // 收集该watcher的所有deps依赖,只有计算属性使用 

  teardown() // 将自身从所有依赖收集订阅列表删除

}

依赖收集流程

依赖收集流程图
当我们实例化一个渲染watcher的时候,执行this.get方法。进行get函数执行pushTarget(),实际上把Dep.target 赋值为当前的渲染 watcher 并压栈,接着执行value = this.getter.call(vm, vm),实际上调用getter方法,触发数据的getter,从而调用dep.depend方法,也就会执行Dep.target.addDep(this):

addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
      dep.addSub(this)
    }
  }
}

此时会做一些逻辑判断(保证同一数据不会被添加多次)后执行 dep.addSub(this),那么就会执行 this.subs.push(sub),也就是说把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页