就以一个最简单的Vue文件开始
new Vue({
render: h => h(App),
}).$mount('#app')
<!--App.vue-->
<template>
<div>
<div>{{oriValue}}</div>
<div>{{oriValueAfterComputed}}</div>
<div>{{oriValueObjectAfterComputed}}</div>
<button @click="oriplus">oriValue加1</button>
<button @click="oriValueObjectAfterComputedSetting">
设置oriValueObjectAfterComputed的值
</button>
</div>
</template>
<script>
export default {
data() {
return {
oriValue:1
}
},
methods:{
oriplus(){
this.oriValue++
},
oriValueObjectAfterComputedSetting(){
this.oriValueObjectAfterComputed = 10
}
},
computed:{
oriValueAfterComputed(){
return this.oriValue+"++computed"
},
oriValueObjectAfterComputed:{
get:function(){
return this.oriValue + "ObjComputed"
},
set:function(newValue){
this.oriValue = newValue
}
}
}
}
</script>
上面的App.vue文件中, export default {}里面包含的就是App这个组件的options,options的computed的属性也是个对象,这个对象设置了oriValueAfterComputed这个属性,因此在Vue的初始化中,会执行一下代码,computed对象的初始化
initComputed
if (opts.computed) { initComputed(vm, opts.computed); }
上面的initComputed方法下面进行下面进行详细介绍(无关代码会忽略,以免影响理解)
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
<!--这里判断是否是服务端渲染,是的话则computed对象的属性值只能取不能设置-->
var isSSR = isServerRendering();
for (var key in computed) {
var userDef = computed[key];
<!--这里是判断computed[key]是一个对象还是方法(例如本例computed对象中oriValueAfterComputed是一个方法;oriValueObjectAfterComputed则是一个对象),如果是对象的话,get方法就是这个对象的get属性值-->
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (!isSSR) {
// create internal watcher for the computed property.
<!--用上面获得的getter方法,创建一个watcher对象,这个watcher的值watcher.value就是用getter方法计算出来的-->
<!--初始化计算watcher的时候lazy为true,在创建watcher的时候不会立马计算value的值,而是把dirty初始化为true,所以在初始化的时候value还是undefined,等到执行render函数读取这个属性的时候, 由于dirty是true,所以会进行一次evaluate计算value的值-->
var computedWatcherOptions = { lazy: true };
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
if (!(key in vm)) {
//设置这个key的setter和getter,实现对这个key值的监听
defineComputed(vm, key, userDef);
} else {
//如果computed属性已经在props,data,inject,methods等对象中被定义,则报警告。不执行computed该属性的监听
}
}
}
defineComputed
function defineComputed (
target,
key,
userDef
) {
<!--非服务端渲染则缓存,缓存的意思是,不是每一次获取computed对象的属性都执行一次上面对应的userDef函数,而是把这个计算值存在对应的watcher对象的value里面,以后使用可以直接获取watcher里面的value值(除非userDef的执行结果更新了,才重新获取)-->
var shouldCache = !isServerRendering();
<!--var userDef = computed[key];上面的initComputed函数有说到,userDef可能是函数可能是对象,如果它是对象的话,则getter函数是这个对象属性get的值-->
if (typeof userDef === 'function') {
<!--关于createComputedGetter和createGetterInvoker方法请看下一段的函数分析,简单来说,
createGetterInvoker是直接执行getter,也就是userDef.get/userDef函数,获得返回值,
每次获取这个key的值,都会执行一遍,返回结果;
createComputedGetter是获取这个key对应的watcher的value值,
如果value值计算结果不发生改变的话,watcher不会重新执行this.getter
-->
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;
}
<!--如果没有设置这个key的set方法,但是使用了this[key的名字]=value,则会报warning-->
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
<!--设置sharedPropertyDefinition的gette/setter,就是设置这个key的setter/getter-->
Object.defineProperty(target, key, sharedPropertyDefinition);
}
createGetterInvoker 和 createComputedGetter
//这个函数就很直接粗暴了,返回一个函数,这个函数的执行就是执行一遍fn
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
function createComputedGetter (key) {
return function computedGetter () {
//获取key对应的watcher
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
//在初始化计算watcher的时候,watcher.dirty是true
//watcher的dirty表示watcher的计算值有变化,需要重新计算,所以执行watcher.evaluate(),watcher.value的值会更新
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
至于这里的dirty为什么是true的时候就代表该watcher需要更新了呢?这是因为当某个属性被重新赋值的时候,会触发watcher.update方法,而这个方法在该watcher.lazy为true的时候,将dirty属性设为true,以此告知这个watcher,需要重新计算value值了。watcher.evaluate执行后,watcher.dirty会设置为false,在下一次update之前,不会再次重新计 算watcher的值
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
compouted初始化完成后,render函数执行,读取计算属性的值
vm.$mount的过程中,updateComponent方法里面有一步是
vnode = render.call(vm._renderProxy, vm.$createElement)
<!--这个是App.vue模板文件-->
<div>
<div>{{oriValue}}</div>
<div>{{oriValueAfterComputed}}</div>
<div>{{oriValueObjectAfterComputed}}</div>
<button @click="oriplus">oriValue加1</button>
<button @click="oriValueObjectAfterComputedSetting">
设置oriValueObjectAfterComputed的值
</button>
</div>
上面App.vue文件的template转换为render函数如下,render函数执行会读取computed对象的属性值(本例子中就是oriValueAfterComputed和oriValueObjectAfterComputed),触发属性的get方法,也就是createComputedGetter返回的方法:computedGetter,返回对应的watcher.value的值,这样我们就可以拿到oriValueAfterComputed和oriValueObjectAfterComputed的值了。
<!--这个是App.vue模板文件编译后的render函数-->
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", [
_c("div", [_vm._v(_vm._s(_vm.oriValue))]),
_c("div", [_vm._v(_vm._s(_vm.oriValueAfterComputed))]), //这里会触发oriValueAfterComputed的getter方法
_c("div", [_vm._v(_vm._s(_vm.oriValueObjectAfterComputed))]), //这里会触发oriValueObjectAfterComputed的getter方法
_c("button", { on: { click: _vm.oriplus } }, [_vm._v("oriValue加1")]),
_c("button", { on: { click: _vm.oriValueObjectAfterComputedSetting } }, [
_vm._v(" 设置oriValueObjectAfterComputed的值 ")
])
])
}
(ps:因为我用的是runtimeOnly版本,所以编译的时候,template里面的内容已经预编译成render函数),如果对模板编译成render函数的过程有兴趣的话,可以看看 vue.esm.js这个vue文件里面的createCompiler函数
本文是我学习源码过程中的一个小记录,以此梳理清楚自己对computed属性的理解和认识,希望如果有朋友发现文章中有任何不够或不正确的地方,请麻烦指出,实在太感谢你们了。