[路飞]_vue2源码学习_dep为什么要创建两份?

228 阅读3分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

问题

为什么要再defineReactive创建dep,且new Observer的时候还要创建dep?

源码

vue源码如下

// src/core/observer/index.js  39行
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
        ...
    } else {
      ...
    }
  }
}
// src/core/observer/index.js  132行
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
        ...
    }
  })
}

现象

能看到Observer里执行了this.dep = new Dep(),创建了dep实例,并挂载在Observer的实例上

然后defineReactive里又执行了const dep = new Dep(),保存在了闭包里

为什么?

思路

data(){
    return {
        obj:{
            foo:{
                a:1
            },
            bar:{
                b:2
            }
        }
    }
}

我们以上述数据为例

  1. defineReactive里的dep只能存储读取当前对象某个属性的watcher,它以对象的key为维度,这个key的值变化了,那就通知这些watcher去update
  2. 执行Vue.set(foo,'b',100)的时候,defineReactive里面的defineProperty给foo创建完属性,这个属性之前并不存在,所以并有对他做依赖收集,所以没办法通知watcher变更,因为不知道通知谁
  3. 那么我们分析,给foo增加属性b,那么本质上是foo变更了(这里注意不是b变更了,b之前都没有,谈不上变更),我们应该通知foo对应的watcher去更新,那我们就得想办法获取到foo对应的watcher
  4. 当我们修改obj的foo属性的时候,如obj.foo = {e:99},其实就是foo发生变更了,那么按上面分析,我们是不是就可以给这时候收集到的对应的watcher存下来,后序给Vue.set(foo,b,1)使用呢?那存在哪呢? --可以存在foo对应的__ob__.dep里。
  5. 这样我们在set的时候,就可以去取出foo的__ob__.dep来通知更新了

那这延伸出一个问题,foo必须是响应式数据,它才有__ob__,那也就意味着Vue.set(obj)传入的obj必须是响应式数据

复盘

data(){
    return {
        obj:{
            foo:{
                a:1
            },
            bar:{
                b:2
            }
        }
    }
}

那么我们再来复盘一下读取和新增属性的流程

  1. 读取obj.foo => 触发get依赖收集,存一份在闭包,另外存一份在foo.__ob__.dep里

  2. obj.foo的值发生变更时,会去通知视图更新,这个watcher对应的是obj.foo属性的变更

  3. 那么当我们Vue(foo,'c',100)时,其实也是foo发生变更了,我们就可以去foo.__ob__.dep里面取出刚才读取obj.foo的watcher,去通知更新

总结

这里其实很绕,可能看一遍不能理清楚,有时觉得自己清楚了,过一会看有理不清楚了。

我们是这么理解的并抓住重点的

  • 修改谁,就是谁变更了,如obj.foo => foo变更

  • 给谁新增属性,也是谁变更了,如Vue.set(foo,'c',100) => foo变更

  • set触发不了c的收集,因为之前没有c,所以不做处理的话,后序修改c,无法通知

  • obj.foo读取foo属性的时候保存依赖watcher,一份给foo修改使用存在闭包,一份给foo新增使用,存在foo的响应式数据内