官方定义
- 类型:
{ [key: string]: Function | { get: Function, set: Function } }
- 详细:计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例...
计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。
上面这几段话其实可以归纳为以下几点:
-
computed
是计算属性,会被混入到Vue
实例中 -
computed
的结果会被缓存,除非依赖的响应式属性变化才会重新计算
如何初始化computed
?
同以往一样,先新建一个Vue
项目,同时加入以下代码:
export default {
name: 'test',
data () {
return {
app: 666
}
},
created () {
console.log('app proxy -->', this.appProxy)
},
computed () {
appProxy () {
debugger
return this.app
}
}
}
F12
打开调试界面,刷新后断点停在了debugger
的位置,同时可以看到右边的调用栈:
appProxy
get
evaluate
computedGetter
created
- ...
瞥到computedGetter
之后,点进去,可以看到:
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
,看看它是在哪里调用的。此处忽略搜索的过程,直接给出我的结论:
Vue
中存在两种初始化computed
的方法:
- 在
option
中初始化 - 在
Vue.prototype.extend
函数中初始化
这两种初始化其实大同小异,我们选择在组件中写computed
,自然断点就会跑到Vue.prototype.extend
函数里:
...
if (Sub.options.computed) {
initComputed$1(Sub);
}
...
initComputed$1
函数:
function initComputed$1 (Comp) {
// 拿到组件的computed
var computed = Comp.options.computed;
for (var key in computed) {
// 循环遍历
defineComputed(Comp.prototype, key, computed[key]);
}
}
显然,这句代码:defineComputed(Comp.prototype, key, computed[key])
将computed
挂载在了组件的原型上,下面来看下它的实现方式:
defineComputed
:
function defineComputed (
target,
key,
userDef
) {
// 判断是否要将结果缓存下来
var shouldCache = !isServerRendering();
// 下面进行分类判断
// 对应的computed是函数的情况
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 (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
// 将sharedPropertyDefinition绑定到组件对象上
Object.defineProperty(target, key, sharedPropertyDefinition);
}
?感觉有点乱,最后再梳理下上边的逻辑:
initComputed
:
- 执行
initComputed
,从Vue
中拿到computed
对象里所有的key
值 - 循环拿到的
key
值,调用defineComputed
函数,把computed
绑定到组件对象上
defineComputed
:
- 判断是否在服务端渲染,是则
computed
的结果会被缓存,不是则不会缓存计算结果 - 由于
computed
存在两种写法,这里也对函数跟对象的写法做了区分
computed
的结果缓存是如何实现的?
上面我们大致梳理了下computed
的初始化逻辑,现在我们回过头来再看一下官方定义,发现其中提到了计算属性会将计算结果缓存下来,那么这个计算结果到底是怎么被缓存下来的呢?
回到defineComputed
defineComputed
里最后将sharedPropertyDefinition
绑定到组件对象上,在代码里面可以看到对sharedPropertyDefinition.get
做了特殊处理,两种情况分别封装了:
createComputedGetter
createGetterInvoker
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
}
}
}
createGetterInvoker
的实现:
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
可以看到,服务端渲染确实是对计算属性的结果不做缓存的,但是我们对结果是如何缓存,依旧是一脸懵逼?
回到最初的断点
刷新页面回到一开始我们在appProxy
中打下的断点,在调用栈中有两个显眼的函数:
evaluate
get
分别点进去,我们可以看到:
evaluate
实现源码:
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
get
实现源码:
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var 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
};
结合上面给出的createComputedGetter
源码我们可以知道,computed
的计算结果是通过Watcher.prototype.get
来得到的,拿到value
以后,在Wathcer.prototype.evaluate
中执行了这样一行代码:
...
this.dirty = false;
聪明的读者肯定猜到了,计算属性是否重新计算结果,肯定跟这个属性有关。接下来我们只要跟踪这个属性的变化,就可以轻松的知道计算属性的缓存原理了。
扫描下方的二维码或搜索「tony老师的前端补习班」关注我的微信公众号,那么就可以第一时间收到我的最新文章。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。