前言
1、Vue源码解析(一)-模版渲染
2、Vue源码解析(二)-MVVM双向绑定
demo
官网给出的demo如下
<div id="app"></div>
new Vue({
el: '#app',
template:
`<div>
<p>Original message is: {{ message }}</p>
<p>Computed reversed message:: {{ reversedMessage }}</p>
</div>`,
data(){
return {
message: 'Hello',
}
},
computed:{
reversedMessage(){
return this.message.split('').reverse().join('')
}
}
})
结果:
Original message: "Hello"
Computed reversed message: "olleH"
源码分析
//判断参数是否包含computed属性
if (opts.computed) { initComputed(vm, opts.computed); }
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
//本例中key=‘reversedMessage’
for (var key in computed) {
//本例中userDef和getter是reversedMessage函数
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
//监听计算属性,设置lazy=true,延迟执行watcher的get方法
watchers[key] = new Watcher(vm,getter,{lazy:true});
//设置可以通过vm[key](本例vm.reversedMessage)方式访问计算属性
defineComputed(vm, key, userDef);
}
}
1、vue对象初始化时会针对computed属性的所有key值分别new一个watcher对象,在Vue源码解析(二)中有详细介绍watcher的原理,当时提到watcher初始化会立即调用一次watcher.get方法,然后实际上可以通过传入{lazy:true}参数来延迟watcher.get方法的执行
var Watcher = function Watcher (vm,expOrFn,options){
//延迟计算
this.lazy = options.lazy;
//还没有计算,所以数据是脏的
this.dirty = options.lazy;
this.value = this.lazy
? undefined
//计算getter值和收集依赖
: this.get();
}
2、defineComputed(vm, key, userDef),将computed属性代理到vm上,通过vm[key]访问computed属性值
function defineComputed (target,key,userDef){
//userDef是function,getter设为userDef或userDef的值
if (typeof userDef === 'function') {
//shouldCache是否缓存,这也是使用computed属性最重要的原因,computed值会被缓存起来,而不是每次重新执行函数生成
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef;
sharedPropertyDefinition.set = null;
//userDef是不是function,getter设为userDef.get,setter设为userDef.set
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: null;
sharedPropertyDefinition.set = userDef.set
? userDef.set
: null;
}
//,将computed属性代理到vm上,通过vm[key]访问computed属性值
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter (key) {
return function computedGetter () {
//shouldCache = true时直接返回缓存值watcher.value
var watcher = this._computedWatchers && this._computedWatchers[key];
//存在脏数据则重新计算watcher的值
if (watcher.dirty) {
watcher.evaluate();
}
//直接返回缓存中watcher的值
return watcher.value
}
}
}
3、前面提到watcher.get方法会延迟执行,那么到底啥时执行呢?这又得提到Vue源码解析(二)中的updateComponent方法,由于本例引用了计算属性{{ reversedMessage }},updateComponent中的render函数则会调用vm.reversedMessage,因此触发第二步的sharedPropertyDefinition.get函数,调用 watcher.evaluate(),最终调用watcher.get()来计算watcher的值和收集依赖。(watcher.get方法将监听vm.reversedMessage的watcher对象和发布vm.message变化的dep对象绑定,因此当vm.message变化时,vm.reversedMessage值也会同步变化)
因此watcher.get是在第一次访问vm.reversedMessage对象时调用的,所以如果模版没有用到{{ reversedMessage }}值的话vm.reversedMessage的值是不会被计算的
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
双向绑定问题
正好之前看到过一个问题vue.js使用computed计算某个属性后,该属性的双向绑定没了,看了本文的源码后大家应该了解了计算属性用在v-model上应该设置setter方法,例如本例中demo应该这么写:
new Vue({
el: '#app',
template:
`<div>
<input v-model="reversedMessage" placeholder="edit me">
<p>Original message is: {{ message }}</p>
<p>Computed reversed message:: {{ reversedMessage }}</p>
</div>`,
data(){
return {
message: 'jixiangwu',
}
},
computed:{
reversedMessage:{
get(){
return this.message.split('').reverse().join('')
},
set(val){
this.message = val.split('').reverse().join('')
}
}
}
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。