# computed原理
当computed依赖的数据发生变化时,computed对应的key会重新计算。否则,则取上次计算的值。
computed具备缓存的功能。
// App.vue
<template>
<div>
<p>fullName</p>
</div>
</template>
<script>
export default {
data() {
return {
firstName: 'hello',
lastName: 'world'
}
},
computed: {
fullName() {
return this.firstName + this.lastName
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 初始化computed选项
初始化时,通过initComputed方法来初始化computed选项。
function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options;
// ...
// 计算属性watcher
if (opts.computed) initComputed(vm, opts.computed)
// ...
}
2
3
4
5
6
7
8
循环options中的computed对象,给每个key添加一个watcher(用户watcher)。对于computed watcher,以options选项中的lazy为true作为标识。
同时,将每个key对应的watcher存储到vm._computedWatchers对象中。此时我们创建给fullName这个key创建了一个watcher,暂时命名为watcher_fullName,该watcher的dirty为true。
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
// vm._computedWatchers,存储计算属性watcher
const watchers = vm._computedWatchers = Object.create(null);
// 是否服务端渲染
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
// computed的写法,一种是fn;一种是对象,包括get、set方法
const getter = typeof userDef === 'function' ? userDef : userDef.get;
if (!isSSR) {
// 计算属性watcher, lazy为true,getter为计算属性key所对应的function
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
调用defineComputed,给每个key,通过defineProperty,自定义其getter、setter。
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
// 通过Object.defineProperty,
// 定义vm.key的descriptor为`sharedPropertyDefinition`(它的get方法为createComputedGetter的返回值,即computedGetter函数)
Object.defineProperty(target, key, sharedPropertyDefinition)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
其中自定义getter,是通过createComputedGetter方法产生。该方法返回一个函数computedGetter,当我们通过vm.fullName访问fullName时,就会触发computedGetter方法。
function createComputedGetter (key) {
// 计算属性中,某个key的get方法,就是’computedGetter‘函数,对计算属性取值时,就会调用get方法(即’computedGetter‘函数)
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
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# computed依赖收集
当我们挂载App.vue时,
- 初始化当前组件的渲染watcher(我们暂时称之为watcher_app)。在执行new Watcher时,首先将
watcher_app入栈,并将Dep.target设置为watcher_app。
stack: [watcher_app];
Dep.target = watcher_app;
2
- 执行this.getter,即
vm._update(vm._render())生成vnode。在执行vm._render过程中,会对fullName取值,此时触发createComputedGetter(fullName)。由于计算属性watcher的lazy为true,初始化时dirty为true,此时调用watcher.evaluate()方法,对fullName进行求值。
function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// dirty为true,计算watcher的value,即执行computed key对应的function
if (watcher.dirty) {
watcher.evaluate();
}
// 渲染watcher依赖收集
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
watcher的evaluate方法,内部会调用this.get方法,并将dirty设置为false。
evaluate() {
// 计算属性watcher计算完成后,设置dirty为false,以后只要依赖的dep没有发生变化,执行计算属性watcher时,就不会重新求值
this.value = this.get()
this.dirty = false
}
2
3
4
5
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 {
popTarget()
}
return value
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
执行this.get方法时:
1) 首先将当前计算属性watcher入栈,并将Dep.target设置为watcher_fullName
stack: [watcher_app, watcher_fullName];
Dep.target = watcher_fullName;
2
2)执行this.getter方法:对于计算属性而言,就是key对应的那个函数。函数内部会对firstName、lastName取值,触发各自的get方法,进行依赖收集。
firstName_dep: { subs:[watcher_fullName] }
lastName_dep: { subs:[watcher_fullName] }
wathcer_fullName: {deps: [firstName_dep, lastName_dep]}
2
3
4
- 当前计算属性watcher出栈
stack: [watcher_app];
Dep.target = watcher_app;
2
- watcher值计算完成后,当前Dep.target仍然有值,为watcher_app。此时调用watcher.depend,循环执行watcher_app中的deps依赖。
function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
//...
// 渲染watcher依赖收集
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
2
3
4
5
6
7
8
9
10
11
循环watcher的deps依赖时,对每个dep依赖,收集当前的渲染watcher(watcher_app).
// watcher depend
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
2
3
4
5
6
7
- 渲染watcher出栈,最终依赖的结果为:
// dep
firstName_dep: { subs:[watcher_fullName, watcher_app] }
lastName_dep: { subs:[watcher_fullName, watcher_app] }
// watcher
wathcer_fullName: {deps: [firstName_dep, lastName_dep]}
watcher_app: {deps: [firstName_dep, lastName_dep]}
2
3
4
5
6
7
整个流程如下:
# computed更新
当firstName或者lastName发生变化时,触发其get方法,此时执行其watcher数组中,每个watcher的update方法,其基本流程如下:
firstName变化 -> getter -> firstName_dep.notify -> 循环执行water.update -> queueWatcher ->
nextTick(flushSchedulerQueue) -> watcher.run -> this.get()
2
注意:
- 在queueWatcher时,由于计算属性watcher(watcher_fullName)在执行一次计算后,dirty设置为false了,所以watcher_fullName并不会进入队列中。只有渲染watcher(watcher_app)进入队列中。
- 执行watcher.update时,如果是计算属性watcher,会重置其dirty为true,方便后续计算。
- 当执行渲染watcher的run方法时,会重新执行其get方法,重新生成vnode进行页面渲染。在重新生成vnode时,会取vm.fullName的值,然后执行computedGetter重新计算新的值。

← watch原理 dom-diff原理 →