initComputed 函数调用执行逻辑如下:
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
首先,会调用 initData 对 data 数据做observe、new Observer、defineReactive 等一系列操作,对 data 中的数据做 getter 和 setter 操作。传送门➡️ (opens new window)
接着,会调用 initComputed 对 computed 数据进行初始化。
# initComputed 函数
源码如下:
var computedWatcherOptions = { lazy: true }; // computed watcher 有缓存机制,lazy 为缓存控制标志
function initComputed (vm, computed) {
// $flow-disable-line
var watchers = vm._computedWatchers = Object.create(null); // 用于收集 computed watcher
// computed properties are just getters during SSR
var isSSR = isServerRendering();
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
// computed getter null 值判断
if (getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
if (!isSSR) {
// 为每个 computed 属性实例化一个 watcher,即:computed-watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
if (!(key in vm)) {
// 调用 defineComputed 函数,设置每个 computed 属性的 setter 和 getter,做代理处理
defineComputed(vm, key, userDef);
} else {
// 断言处理
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);
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(("The computed property \"" + key + "\" is already defined as a method."), vm);
}
}
}
}
computed 的结果有缓存机制,通过实例化 watcher 时设置参数 lazy 为 true 进行设置。
计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。注意,如果某个依赖 (比如非响应式 property) 在该实例范畴之外,则计算属性是不会被更新的。
initComputed 函数的执行逻辑为:
- 定义 vm._computedWatchers,所有的 computed-watcher 都会被收集在这里。
- 获取 computed 的 getter 函数,如果该 computed 属性的值是函数的话直接把该函数作为 getter,如果是对象的话,则需要设置对象的 get 函数。有以下两种写法:
export default {
computed: {
// 函数写法
sum() {
return this.num1 + this.num2
},
// 对象写法
sum2: {
get: function () {
return this.num1 + this.num2
}
}
}
}
- 接着,为每个 computed 属性实例化一个 watcher,即:computed-watcher。
- 调用 defineComputed 函数,设置每个 computed 属性的 setter 和 getter,做拦截处理。
# defineComputed 函数
function defineComputed (
target,
key,
userDef
) {
var shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop;
sharedPropertyDefinition.set = userDef.set || noop;
}
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
defineComputed 函数的作用是:调用 Object.defineProperty 设置每个 computed 属性的 setter 和 getter,为每个 computed 属性做代理处理。getter 的核心是通过调用 createComputedGetter 为每个 computed 属性生成 getter 函数,setter 函数如果没有手动定义的话则会设置为空函数。
# createComputedGetter 函数
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
createComputedGetter 函数是 computed 逻辑中最重要的一环。
当获取 computed 数据时,会触发 computed 的 getter,即:createComputedGetter 返回的 computedGetter 函数;接着,判断 computed-watcher 的 dirty 值是否为 true,如果为 true,表示缓存的数据已经脏了,需要重新计算;然后,如果当前有 watcher 的话,则会调用 watcher.depend,作用是建立 data 与 render-watcher 的联系;最后,返回 watcher.value,也就是当前 computed 属性的值。
# watcher.evaluate
watcher.evaluate 的作用是调用 computed-watcher 的 get 函数,即:触发 computed 的 getter 函数,重新计算 computed 的值,并将 dirty 设置为 false。函数源码如下:
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
# watcher.depend
watcher.depend 的作用是建立 computed-watcher 的所有 deps(即:依赖的所有 data)与 render-watcher 的依赖关系。函数源码如下:
Watcher.prototype.depend = function depend() {
var i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
};
函数很简单,通过遍历 computed-watcher 实例上的 deps 列表,调用每个列表项 dep 上的 depend 方法。
那么问题来了,this.deps 是什么?data 数据怎么建立与 render watcher 的关系?
# 流程
用下面例子来说明:
<template>
<div id="app">{{ sum }}</div>
</template>
<script>
export default {
data() {
return {
num1: 1,
num2: 2
}
},
computed: {
sum() {
return this.num1 + this.num2
}
}
}
</script>
首先,当 render-watcher 实例化的时候,Dep.target 为 render-watcher,会触发 computed 中属性 sum 的 getter 函数,即以下函数。
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
接着,获取当前的 computed-watcher,由于是首次获取值,所以 dirty 为 true,会调用 watcher.evaluate 进行值的计算,watcher.evaluate 内部会触发 computed-watcher 的 get 函数。
computed-watcher 的 get 函数:首先会将 Dep.target 设置为 computed-watcher,接着调用 computed 的 getter 函数,内部会获取 data 数据(例子中是 num1 和 num2),进而触发 data 数据的 getter 函数,然后,将 Dep.target 恢复为 render-watcher,最后调用 cleanupDeps 函数。
watcher.evaluate 达到目的:
- 在 dep 的 subs 中保存当前的 computed-watcher。
- 在 computed-watcher 的 deps 保存所有新的 dep。到此,this.deps 为保存 num1 和 num2 的 dep 实例列表,每个 dep 实例内部的 subs 中又保存着 computed-watcher。
接着,调用 watcher.depend,此时的 Dep.target 恢复为 render-watcher,再看下函数内部逻辑:
Watcher.prototype.depend = function depend() {
var i = this.deps.length;
while (i--) {
this.deps[i].depend(); // 调用 dep 实例的 depend 函数
}
};
class Dep {
// ...
depend(info?: DebuggerEventExtraInfo) {
if (Dep.target) {
Dep.target.addDep(this)
if (__DEV__ && info && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
...info
})
}
}
}
// ...
}
这里对 computed-watcher 实例内部的 deps 进行遍历,然后调用每个 dep 的 depend 方法。由于此时的 Dep.target 为 render-watcher,所以会将 render-watcher 添加到每个 dep 的 subs 中,最终建立 data 与 render-watcher 的联系。
当 computed 属性中依赖的 data 数据更新时,流程是这样的:
- 触发 data 数据的 setter,通知该 data 数据的所有 watcher 更新数据。
- 先通知 computed-watcher。computed-watcher 不会立刻进行数据更新,而是将 dirty 设置为 true,表示当前 computed 的数据已经脏了,需要重新计算。
- 再通知 render-watcher。render-watcher 会重新获取 computed 的值,进而触发 computed 的 getter 函数。
- 进入 computedGetter 函数,由于 watcher.dirty 为 true,会执行 watcher.evaluate 重新计算 computed 的值,进而获取到最新的值。