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 (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
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)
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
首先调用 this.get() 将它的返回值赋值给 this.value ,来看 get 函数:
// src/core/observer/watcher.js
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
// src/core/observer/watcher.js
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
这里先给一个结论:计算属性
computed
的本质是computed Watcher
,其具有缓存。一张图了解下
computed
的实现:initComputed
方法。对应源码src/core/instance/state.js
的 169 行。initComputed
函数拿到computed
对象然后遍历每一个计算属性。判断如果不是服务端渲染就会给计算属性创建一个computed Watcher
实例赋值给watchers[key]
(对应就是vm._computedWatchers[key]
)。然后遍历每一个计算属性调用defineComputed
方法,将组件原型,计算属性和对应的值传入。defineComputed
定义在源码src/core/instance/state.js
210 行。首先定义了
shouldCache
表示是否需要缓存值。接着对userDef
是函数或者对象分别处理。这里有一个sharedPropertyDefinition
,我们来看它的定义:sharedPropertyDefinition
其实就是一个属性描述符。回到
defineComputed
函数。如果userDef
是函数的话,就会定义getter
为调用createComputedGetter(key)
的返回值。而
userDef
是对象的话,非服务端渲染并且没有指定cache
为false
的话,getter
也是调用createComputedGetter(key)
的返回值,setter
则为userDef.set
或者为空。所以
defineComputed
函数的作用就是定义getter
和setter
,并且在最后调用Object.defineProperty
给计算属性添加getter/setter
,当我们访问计算属性时就会触发这个getter
。userDef
是函数还是对象,最终都会调用createComputedGetter
函数,我们来看createComputedGetter
的定义:computedGetter
函数首先通过this._computedWatchers[key]
拿到前面实例化组件时创建的computed Watcher
并赋值给watcher
。接着有两个
if
判断,首先调用evaluate
函数:首先调用
this.get()
将它的返回值赋值给this.value
,来看get
函数:get
函数第一步是调用pushTarget
将computed Watcher
传入:可以看到
computed Watcher
被 push 到targetStack
同时将Dep.target
置为computed Watcher
。而Dep.target
原来的值是渲染Watcher
,因为正处于渲染阶段。回到get
函数,接着就调用了this.getter
。回到
evaluate
函数:执行完
get
函数,将dirty
置为false
。回到
computedGetter
函数,接着往下进入另一个if
判断,执行了depend
函数:这里的逻辑就是让
Dep.target
也就是渲染Watcher
订阅了this.dep
也就是前面实例化computed Watcher
时候创建的dep
实例,渲染Watcher
就被保存到this.dep
的subs
中。在执行完
evaluate
和depend
函数后,computedGetter
函数最后将evaluate
的返回值返回出去,也就是计算属性最终计算出来的值,这样页面就渲染出来了。