computed选项在initState
阶段初始化
function initState(){
if(opts.computed) {
initComputed(vm,opts.computed)
}
}
整个流程图
initComputed
- 为每个computed属性设置一个Watcher
- 为属性设置读取访问器getter,并添加到实例上
function initComputed (vm, computed) {
//
var watchers = vm._computedWatchers = Object.create(null)
// 是否服务端渲染
var isSSR = isServerRendering();
// 遍历传入的computed
for (var key in computed) {
var userDef = computed[key];
// computed可以写成两种格式 function 或者 提供了get方法的对象
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
if (!isSSR) {
// 为计算属性添加一个Watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
// 实例上不存在时,把它添加到实例上
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else if (process.env.NODE_ENV !== 'production') {
// 校验computed重名, data > props > methods
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);
}
}
}
}
Watcher
- 这里的watcher,设置
lazy为true
,作用就是新建实例时不会马上读取它的值,同时用于缓存计算结果的标识; - 把用户设置的 computed 函数存放到 watcher.getter 中,用于读取属性时取值;
- watcher.value 存放值
function Watcher (vm,expOrFn,cb,options,isRenderWatcher) {
this.lazy = options.lazy
this.dirty = this.lazy;
this.getter = expOrFn; // 用户设置的函数,存放到getter中
this.value = undefined // 初始化时不会主动求值
};
defineComputed
为属性设置读取访问器,并添加到实例上
function defineComputed (target,key,userDef) {
var shouldCache = !isServerRendering();
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = userDef.set || noop;
Object.defineProperty(target, key, sharedPropertyDefinition);
}
例: 代理到实例上的 computed 属性
vm.key = get function createComputedGetter(){}
createComputedGetter
这里设置属性的getter返回一个闭包函数, 在读取属性时实际被调用的就是它.在前面的处理过程中已经将该属性对应的Watcher存放到实例的_computedWatchers
里, 被调用时从当前实例中取出相应属性的Watcher去求值
// 存入 _computedWatchers 的key
vm._computedWatchers: {
key1: Watcher,
key2: Watcher
}
- 初次读取属性时 watcher.dirty 为 true, 也就是在上面新建Watcher时,传入
lazy
的值。现在调用Watcher.prototype.evaluate
去求值 - 再次读取该属性时,由于前一次已经把 dirty 设置为了 false,所以就略过了求值这一步,直接返回了上次保存在watcher.value中的值
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
}
}
}
Watcher.prototype.evaluate
computed初次被求值后设置 dirty 为 false,后面再读取属性时就不会进入该方法重新求值
Watcher.prototype.evaluate = function() {
this.value = this.get();
this.dirty = false;
};
Watcher.prototype.get
this.getter
就是用户传入的computed属性对应的函数, 在上文中有说明. 将当前的Watcher设置为Dep.target
以标明当前正在使用的Watcher,函数调用返回的值保存在当前watcher.value
中
Watcher.prototype.get = function() {
// 将自身设置到全局位置
pushTarget(this);
var value;
var vm = this.vm;
// getter 就是computed中用户自己设置的函数,在这里被调用
value = this.getter.call(vm, vm);
return value
};
依赖的数据发生变化
在调用函数的过程中,如果访问了data中的数据,则该数据就会收集此Watcher到该数据的dep.subs中
例:
data(){
return {
test: "test"
}
},
computed: {
showTest(){
return this.test
}
},
created(){
console.log(this.showTest)
}
// 本例在created访问showTest的过程中, 数据test收集了showTest 创建的 Watcher
obj: {
test: "test",
__ob__: {
dep: {
subs: [Watcher]
}
}
}
期间如果test数据发生了变化,该数据在setter中触发notify更新通知
set: function(newVal) {
dep.notify();
}
取出该数据收集的所有Watcher,依次update
Dep.prototype.notify = function notify () {
var subs = this.subs.slice(); // dep.subs中存放的是收集自己dep的Watcher
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
在初始化computed的时候就设置了Watcher.lazy为true, 所以此时再重新设置Watcher.dirty为true,目的就是在下次访问此计算属性时,需要重新求值
Watcher.prototype.update = function update () {
if (this.lazy) {
this.dirty = true;
}
};