Vue—依赖收集原理

2022/08/16 Vue 共 5293 字,约 16 分钟

响应式系统

3663528165-5b40c9f1524cc

每个组件实例都有相应的 Watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 Watcher 重新计算,从而使它关联的组件得以更新。

这里有三个重要的概念 ObserveDepWatcher

  • Observe 类主要给响应式对象的属性添加 getter/setter 用于依赖收集与派发更新
  • Dep 类用于收集当前响应式对象的依赖关系
  • Watcher 类是观察者,实例分为渲染 watcher、计算属性 watcher、侦听器 watcher 三种

源码解析

initState

源码地址:src/core/instance/state.tsinitState 中:

export function initState(vm: Component) {
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)         // 初始化 props

  // Composition API
  initSetup(vm)

  if (opts.methods) initMethods(vm, opts.methods)   // 初始化 methods
  if (opts.data) {
    initData(vm)                                    // 初始化 data
  } else {
    const ob = observe((vm._data = {}))
    ob && ob.vmCount++
  }
  if (opts.computed) initComputed(vm, opts.computed) // 初始化 computed
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)                        // 初始化 watch
  }
}

Observer/defineReactive

源码地址:src/core/observer/index.ts

export function observe(
  value: any,
  shallow?: boolean,
  ssrMockReactivity?: boolean
): Observer | void {
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (...) {
    ob = new Observer(value, shallow, ssrMockReactivity)
  }
  return ob
}

这个方法主要用 data 作为参数去实例化一个 Observer 对象,Observer 是一个 Class,用于依赖收集和 notify 更新,Observer 的构造函数使用 defineReactive 方法给对象的键响应式化,给对象的属性递归添加 getter/setter,当 data 被取值的时候触发 getter 并收集依赖,当被修改值的时候先触发 getter 再触发 setter 并派发更新

export class Observer {
  dep: Dep
  vmCount: number // number of vms that have this object as root $data

  constructor(public value: any, public shallow = false, public mock = false) {
    // this.value = value
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (isArray(value)) {
      // 数组的响应式处理
      for (let i = 0, l = value.length; i < l; i++) {
        observe(value[i], false, this.mock)
      }
    } else {
      // 遍历对象的每一个属性并将它们转换为 getter/setter
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) { // 把所有可遍历的对象响应式化
        const key = keys[i]
        defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }
}

export function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: boolean
) {
  const dep = new Dep() // 在每个响应式键值的闭包中定义一个 dep 对象

  const property = Object.getOwnPropertyDescriptor(obj, key)

  // 如果之前该对象已经预设了 getter/setter 则将其缓存,新定义的 getter/setter 中会将其执行
  const getter = property && property.get
  const setter = property && property.set

  let childOb = !shallow && observe(val, false, mock)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val // 如果原本对象拥有 getter 方法则执行
      if (Dep.target) { // 如果当前有 watcher 在读取当前值
        dep.depend()    // 那么进行依赖收集,dep.addSub
        if (childOb) {
          childOb.dep.depend()
          if (isArray(value)) {
            dependArray(value)
          }
        }
      }
      return isRef(value) && !shallow ? value.value : value
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val // 先执行 getter
      if (!hasChanged(value, newVal)) { // 如果跟原来的值一样则不管
        return
      }

      if (setter) {
        setter.call(obj, newVal) // 如果原本对象拥有 setter 方法则执行
      } else if (getter) {
        return
      } else if (!shallow && isRef(value) && !isRef(newVal)) {
        value.value = newVal
        return
      } else {
        val = newVal
      }
      dep.notify() // 如果繁盛变更,则通知更新,调用 watcher.update()
    }
  })

  return dep
}

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

setter 的时候首先执行 getter,并且比对旧值是否变化,如果发生变更,则 dep 通知所有 subs 中存放的依赖数据的 Watcher 实例 update 进行更新,这里 update 中会 queueWatcher() 异步推送到调度者观察者队列 queue 中,在 nextTickflushSchedulerQueue() 把队列中的 watcher 取出来执行 watcher.run 且执行相关钩子函数

Dep

上面多次提到一个关键词 Dep,它是依赖收集的容器,或者称为依赖收集器,它记录了哪些 Watcher 依赖自己的变化,或者说哪些 Watcher 订阅了自己的变化

源码地址:src/core/observer/dep.ts

let uid = 0 // Dep 实例的id,为了方便去重

export default class Dep {
  static target?: DepTarget | null // 当前是谁在进行依赖的收集
  id: number
  subs: Array<DepTarget>           // 观察者集合

  constructor() {
    this.id = uid++                // Dep 实例的id
    this.subs = []                 // 存储收集器中需要通知的 Watcher
  }

  addSub(sub: DepTarget) {}         // 添加一个观察者对象

  removeSub(sub: DepTarget) {}      // 移除一个观察者对象

  depend(info?: DebuggerEventExtraInfo) {} // 依赖收集,当存在 Dep.target 时把自己添加到观察者的依赖中

  notify(info?: DebuggerEventExtraInfo) {} // 通知所有订阅者
}

Dep.target = null
const targetStack: Array<DepTarget | null | undefined> = []

export function pushTarget(target?: DepTarget | null) { // 将 Watcher 观察者实例设置给 Dep.target,用于依赖收集,同时将该实例存入 target 栈中
  targetStack.push(target)
  Dep.target = target
}

export function popTarget() { // 将观察者实例从 target 栈中去除并设置给 Dep.target
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

这里 Dep 的实例中的 subs 收集的依赖就是 watcher,它是 Watcher 的实例,将来用来通知更新

Watcher

源码地址:src/core/observer/watcher.ts

export default class Watcher implements DepTarget {
  ...

  constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null,
    isRenderWatcher?: boolean       // 是否时渲染 watcher 的标志位
  ) {
    ...
    if (isFunction(expOrFn)) {
      this.getter = expOrFn          // 在 get 方法中执行
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.value = this.lazy ? undefined : this.get() // lazy 主要针对计算属性,已办用于求值计算
  }

  // 获取 getter 的值并重新进行依赖收集
  get() {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e: any) {
      ...
    } finally {
      ...
      popTarget()         // 将观察者实例从 target 栈中取出并设置给 Dep.target
      this.cleanupDeps()
    }
    return value
  }

  addDep(dep: Dep) {}      // 添加一个依赖关系到 Deps 集合中

  cleanupDeps() {}         // 清理 newDeps 里没有的无用 watcher 依赖

  update() {}              // 调度者接口,当依赖发生变化时进行回调

  run() {}                 // 调度者工作接口,将被调度者回调

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

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

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

get 方法中执行的 getter 就是在一开始 new 渲染 watcher 时传入的 updateComponent = () => { vm._update(vm._render(), hydrating) },这个方法首先 vm._render() 生成渲染 VNode 树,在这个过程中完成对当前 Vue 实例 vm 上的数据访问,触发相应响应式对象的 getter,然后 vm._update()patch

参考文章

https://juejin.cn/post/6844903702881386504