Skip to content
On this page

计算属性

js
// instance/state.js
if (opts.computed) initComputed(vm, opts.computed)

function initComputed (vm, computed) {
  const watchers = vm._computedWatchers = Object.create(null) // vm中定义计算watcher
  const isSSR = isServerRendering() // 服务端渲染判断 false
  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get // 获取定义
    if (!isSSR) {
      watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOpt){
        // computedWatcherOptions === { lazy: true }
      }
    }
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else {
      // 与props或者data属性重名了
    }
  }
}

Computed 类 Watcher

js
// observer/watcher.js
class Watcher {
  constructor (vm, expOrFn, cb, options, isRenderWatcher) {
    // vm, getter, noop, { lazy: true }, undeined
    this.vm = vm
    ...
    if (options) {
      this.lazy = !!options.layz // lazy = true
    }
    ...
    this.dirty = this.lazy
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    }
    
    this.value = this.lazy ? undefined : this.get()
    // lazy = true, 第一次初始化watcher实例的时候是不计算的,延迟计算
  }
  evaluate () {
    // 专门给 计算watcher使用的
    this.value = this.get()
    this.dirty = false
  }
}

Computed 计算的时机

上面知道在创建watcher的时候是延迟计算的,那么真正的计算,获取值的时机是defineComputed

defineComputed

js
// instance/state.js
function defineComputed (target, key, userDef) {
  const shouldCache = !isServerRendering() // true
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
    	?	createComputedGetter(key) // 返回一个函数
    	: createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    // 当设置的computed 是一个对象的时候
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

createComputedGetter

js
function createComputedGetter (key) {
  return function computedGetter () { // _render 创建vnode过程中访问
    const watcher = this._computedWatchers && this._computedWatchers[key] 
    // this = vm,拿到vm实例中对应的计算watcher
    if (watcher) {
      if (watcher.dirty) { // 第一次计算的时候true
        watcher.evaluate()
        // 这一步执行watcher传入的getter,这一步中 Dep.target会变成当前 computedWatcher,那么计算过程中所需要的各个data/props则会收集到这个watcher加入他们的订阅者队列
        // evaluate过程后 Dep.target 出栈当前的 computedWatcher
      }
      if (Dep.target) { 
        // 假如还有上一层watcher,如渲染watcher或者另一个调用的computedWatcher
        watcher.depend()
        // dep 的订阅者添加 渲watcher,watcher依赖池添加dep,这些dep是计算过程中收集的
      }
      return watcher.value // 返回计算值
    }
  }
}

当修改computed所依赖的数据

从之前defineReactive中可以知道,修改computed所以依赖的(data、props),会重新触发watcher 的update 方法

js
// observer/watcher.js
update () {
  if (this.lazy) {
    this.dirty = true; // 更改dirty标识位,标记为脏数据
  } else {
    ...
  }
}

然后由于在上面计算过程中,computed属性的依赖数据的订阅者会添加渲染watcher,因此会触发(data、props)的watcher的update,也就是queueWatcher方法,将渲染watcher推入更新队列

以前版本中,计算watcher 还会有专门为 调用它的 渲染watcher 设置一个dep实例用于存放渲染watcher,但依赖的data就不会收集这个渲染watcher,这样个人感觉不利于维护,对于这个特殊的dep实例难以理解,换成新版本时,直接在依赖项收集渲染watcher更符合思维惯性