废话不说,直接看代码:
const computedWatcherOptions = {lazy: true}
function initComputed(vm: Component, computed: Object) {
// $flow-disable-line
const watchers = (vm._computedWatchers = Object.create(null))
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
这里遍历了 computed 里面的每一个属性,并且为每一个属性初始化了一个 Watcher 对象。这样,当我们在 computed 里面访问 data 里面的属性时,就可以收集到依赖了。注意到这里传入了 { lazy: true },我们看看会有什么效果:
this.dirty = this.lazy // for lazy watchers
...
this.value = this.lazy
? undefined
: this.get()
该属性仅仅是标记了当前数据是 “脏的”,并且不会立即求值。所谓 “脏的” 指的是当前值已经脏了,需要重新求值了,这个后面会再提到。
然后我们看看 defineComputed 做了啥:
export function defineComputed(
target: any,
key: string,
userDef: Object | Function
) {
// 不考虑服务端渲染,这里为 true
const shouldCache = !isServerRendering()
// 只看 computed 值为函数的情况
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
这里执行了 createComputedGetter 这个方法:
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
}
}
}
当我们第一次访问计算属性的时候会触发 get,由于 dirty 为 true,所以这里会走 watcher.evaluate 进行求值,并将 this.dirty 置为 false,这样下次再对 computed 进行求值的时候就不会执行 watcher.evaluate() 了,这样就实现了缓存功能。
evaluate () {
this.value = this.get()
this.dirty = false
}
而当 computed 依赖的数据变化的时候,会触发 Watcher 的 update:
update () {
/* istanbul ignore else */
// computed
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 入队
queueWatcher(this)
}
}
这里仅仅是把 dirty 又重置为了 true 以使得下次对 computed 进行求值的时候重新执行 watcher.evaluate()。
缓存功能分析完了,我们来看看下面这两段段代码做了什么:
if (Dep.target) {
watcher.depend()
}
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
这里有点难理解,我们用一个例子来说明:

首次渲染的时候组件会实例化一个 Watcher 对象,同时会触发对 description 的求值,这里又会实例化一个 Watcher,而 description 中对 fullName 进行求值,又会实例化一个 Watcher。这样就形成了一个依赖栈,靠近栈底的元素会依赖其上面的元素。
当执行 fullName 的时候,由于其依赖了 firstName 和 secondName,所以它会被添加进两者的 dep 中。收集完后会执行 popTarget(),此时 Dep.target 指向 description 的 Watcher,然后会执行 watcher.depend() 。注意这里的 watcher 还是 fullName 的,即 fullName 依赖啥,其他依赖 fullName 的 Watcher 也需要跟它有同样的依赖。举个例子:儿子依赖老爸,老爸是个啃老族依赖父母,所以孙子也间接依赖了爷爷奶奶。同样的,组件的 Watcher 也是同理。
我们调试下这段代码,发现跟我们的分析是一致的:
