vue-computed原理
结论
1、computed属性也是响应式的;(前提是其依赖的是响应书数据,若如下则计算属性将不会更新)】
computed: {
now: function () {
return Date.now()
}
}
2、computed属性是有缓存功能的,只有依赖的数据发生改变,才会进行重新计算;
3、页面的刷新不是computed属性通知的,而是computed依赖的data通知的;
4、一个订阅者对应一个watcher。而一般的data是数据源,每个数据源都有一个Dep,Dep中方的是一个Watcher列表;当data发生改变时,会通知Dep中的Watcher进行更新。
监听过程描述
在初始化vue实例时,会调用InitState()方法,如果配置了computed属性,会调用initComputed方法;
其他的属性,如:data、prop、methods和watch也是同样的操作。
使用发布订阅模式来监听计算属性,也会监听互相依赖的属性。
当依赖链的一环数据发生变化时,都会触发Dep的nofity()方法,会通知Watcher去更新数据,即执行update()方法。
源码分析:
第一步:
function initComputed (vm, computed) {
//创建一个空对象给_computedWatchers
var watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
var isSSR = isServerRendering();
for (var key in computed) {
var userDef = computed[key];
//如果计算属性不是一个方法,name则必须为其定义get方法,否则会报错
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (!isSSR) {
// 为每一个计算属性创建一个观察者,传入vue实例和{lazy:true}
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions //初始化定义为{ lazy: true }
);
}
// 判断计算属性是否在data和prop中存在,不存在继续执行,否则宝座
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else {
//错误提示
}
}
}
第二步:
function defineComputed (
target, //vue实例
key, //计算属性
userDef //计算属性的定义
) {
if (typeof userDef === 'function') {
//为计算属性计算get/set方法
} else {
//为计算属性计算get/set方法
}
if (sharedPropertyDefinition.set === noop) {
//报错,计算属性必须要定义get/set方法。
//如果是一个方法,并且返回值依赖了data或者prop中的属性,vue会自动添加get/set.否则需要手动定义
}
//将计算属性绑定在vue实例上
Object.defineProperty(target, key, sharedPropertyDefinition);
}
第三步 获取计算属性的get方法,computed可以缓存的秘密就在get方法中
function createComputedGetter(key) {
return function computedGetter() {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
//初始化的计算属性的watcher,dirty属性都为true.
//因为初始化了{lazy:true},在watcher的构造方法中,dirty=lazy
if (watcher.dirty) {
watcher.evaluate();
}
//如果当前有在计算的watcher,则表明两个Key之间有依赖关系
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
第四步 当计算属性update时,会执行this.dirty为true,因为第一步中new Watcher时,computedWatcherOptions //初始化定义为{ lazy: true }
Watcher.prototype.update = function update () {
/* istanbul ignore else */
console.error('watch-update!!!');
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
第五步 当页面更新时,会执行第三步的代码,调用watcher.evaluate(),从而调用get方法,并把dirty=false;
Watcher.prototype.evaluate = function evaluate() {
this.value = this.get();
this.dirty = false;
};
更新过程描述
1、当computed绑定的数据发生改变时,触发对应的Dep.prototype.notify()方法,通知观察者watcher进行update,update将dirty属性设置为true,并不是computed的watcher去通知页面更新。
2、同时触发页面更新,页面会去读取computed的值,发现dirty属性为true,因此会重新计算,不会读取缓存。
问答
Q : computed依赖的属性发生变化到页面发生变化,流程是怎样的?
A : 详见更新过程描述。computed月老身份的由来。页面使用了computed属性时,computed的订阅中心会添加页面的watcher,于此同时,computed会将页面介绍给自己依赖的data,这样data的订阅中心既有了computed属性,也有了页面。