Skip to content

initComputed

javascript
function initComputed(vm: Component, computed: Object) {
  // 保存所有的 computed watcher
  const watchers = (vm._computedWatchers = Object.create(null))

  // 遍历 computed
  for (const key in computed) {
    // 获取用户定义的 value,即 userDef,可能是函数,也可能是对象
    const userDef = computed[key]
    // 如果是对象,getter 就是 userDef.get
    const getter = isFunction(userDef) ? userDef : userDef.get

    // 创建 computed watcher 并保存进 vm._computedWatchers 中
    // create internal watcher for the computed property.
    watchers[key] = new Watcher(
      vm,
      getter || noop, // expOrFn
      noop, // cb
      computedWatcherOptions // { lazy: true },用于标识 computed watcher
    )

    if (!(key in vm)) {
      // 生成 computed watcher 后通过 defineComputed 并传入vm、key、userDef 将
      // computed 代理到 vm 上,实现模板中使用 computed 会去 vm 上找,由于
      // computed 要实现懒执行,Object.defineProperty 中的 get 不能马上就把
      // 用户定义的函数执行,包装成高阶函数,返回的函数内部
      // this._computedWatchers[key] 取出对应的 computed watcher,
      // 判断一下如果 watcher.dirty 为真,说明不走缓存,需要执行用户定义的函数,
      // 通过 watcher.evaluate 方法内部执行 this.value = this.get() 获取
      // 初始值或最新值,即 this.getter.call(vm, vm),再 this.dirty = false 保证之
      // 后走缓存,最后返回 watcher.value。
      // 但依赖的值变化时需要 computed watcher 更新,不走缓存。因此我们需要让依赖
      // 的值和 computed watcher 完成互相收集。在 evaluate 中我们执行了 this.get()
      // 会执行用户定义的函数,而函数内部 this.xxx 使用到 data 中的数据就会触发
      // Object.defineProperty 的 get,让依赖的值和 computed watcher 完成了互相
      // 收集。
      // 当依赖的值变化时,update 方法中判断一下 this.lazy 是否为真,为真说明是
      // computed watcher,需要让我们定义的函数重新执行,因此 this.dirty = true,
      // 当后续访问时,get 中发现 watcher.dirty 为真,会执行 watcher.evaluate
      // 获取最新的值。此时我们需要重新执行 render 函数,去 vm 上重新访问 computed,
      // 获取最新的值,也就是需要更新视图。但此时依赖的值改变时视图并不会更新。
      // 先 initState, 再 $mount,所以先有 computed watcher, 才有 render watcher,
      // 但 Dep.target 先是 render watcher, 才是computed watcher,
      // 由于 computed watcher 初始化构造器中不执行 this.get,Dep.target 为空,
      // render watcher 初始化默认执行 this.get, Dep.target = render watcher,
      // this.get() 内部执行 render 函数时访问 computed, 触发高阶函数返回的那个函数,
      // 因为是初始化, watcher.dirty 为真,从而触发 this.evaluate(),内部执行
      // this.get() 获取初始值,pushTarget 会将 Dep.target 更新为 computed watcher,
      // this.getter.call(vm, vm) 执行我们定义的函数去访问 computed 所依赖的值,
      // 触发依赖的值的 get, 完成了依赖的值的 dep 和 computed watcher 的互相收集, 
      // 依赖的值变化时会让 computed watcher 更新, update 中 this.dirty = true。
      // 我们应该让依赖的值的 dep 收集 render watcher, 实现依赖的值变化, 视图更新,
      // 视图更新就会重新访问 computed,此时高阶函数返回的函数中通过
      // this.dirty = true 从而执行 watcher.evaluate,内部执行 this.get 获取到新值, 
      // 再 this.dirty = false 之后走缓存。但是依赖的值的 dep 收集不到
      // render watcher,因此 computed 获取初始值,也就是第一次执行 this.get() 时,
      // pushTarget(this) 让 computed watcher 把 render watcher 覆盖了, 无法
      // 收集到, 当依赖的值变化时,无法通知 render watcher 更新, 除非模板中也正好使用
      // 了依赖的值。为了解决这个问题,使用栈存储 watcher,render watcher 会先入栈, 
      // computed watcher 会后面入栈, 依赖的值的 dep 和 computed watcher 互相收集完
      // 后会 popTarget, 此时出栈并将 Dep.target 设置为栈顶, 此时就是
      // render watcher,获取 computed 初始值时会执行 evaluate,执行完后判断
      // Dep.target 是否有值, 如果有值就要让依赖的值的 dep 收集 render watcher,
      // 依赖的值的 dep 存放在 computed wathcer 的 deps 中, 这就是为什么
      // computed watcher 和 dep 要互相收集的原因。通过调用 watcher.depend, 
      // 内部让每一个依赖的值的 dep 调用 depend(), 完成依赖的值的 dep 和
      // render watcher 的互相收集,实现依赖的值变化视图也会更新。
      defineComputed(vm, key, userDef)
    } else if (__DEV__) {
      if (key in vm.$data) {
        // key 已经在 data 中定义了
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        // key 已经在 props 中定义了
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        // key 已经在 methods 中定义了
        warn(
          `The computed property "${key}" is already defined as a method.`,
          vm
        )
      }
    }
  }
}

class Watcher implements DepTarget {
  vm?: Component | null
  cb: Function
  id: number
  lazy: boolean
  dirty: boolean
  getter: Function
  value: any

  constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null
  ) {
    // 处理 options
    if (options) {
      this.lazy = !!options.lazy
    } else {
      this.lazy = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    // 用于控制 computed watcher 是懒惰的,走缓存。
    // 值为 true 不走缓存,初始化的值和 lazy 值一样,都是 true。
    this.dirty = this.lazy // for lazy watchers
    if (isFunction(expOrFn)) {
      // 对于 render watcher 来说 expOrFn 就是 updateComponnet,是函数。
      // 对于 computed watcher 来说 expOrFn 就是 userDef 或 userDef.get
      this.getter = expOrFn
    } else {
      // ...
    }
    // 实现用户 computed 初始化不执行,在真正使用时才执行。
    this.value = this.lazy ? undefined : this.get()
  }
}

function defineComputed(
  target: any,
  key: string,
  userDef: Record<string, any> | (() => any)
) {
  if (isFunction(userDef)) {
    sharedPropertyDefinition.get = createComputedGetter(key)
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get || noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter(key) {
  return function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

depend() {
  let i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}