Vue源码学习(十一):计算属性computed初步学习
好家伙,
1.Computed实现原理
if (opts.computed) {
initComputed(vm,opts.computed);
}
function initComputed(vm, computed) {
// 存放计算属性的watcher
const watchers = vm._computedWatchers = {};
for (const key in computed) {
const userDef = computed[key];
// 获取get方法
const getter = typeof userDef === 'function' ? userDef : userDef.get;
// 创建计算属性watcher
watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
defineComputed(vm, key, userDef)
}
}
computed依赖跟踪的处理逻辑与watcher相似
1.1.watcher
每个计算属性也都是一个watcher
,计算属性需要表示lazy:true,这样在初始化watcher时不会立即调用计算属性方法
class Watcher {
constructor(vm, exprOrFn, callback, options) {
this.vm = vm;
this.dirty = this.lazy
// ...
this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
}
}
1.2.dirty属性
默认计算属性需要保存一个dirty属性,用来实现缓存功能
function defineComputed(target, key, userDef) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
} else {
sharedPropertyDefinition.get = createComputedGetter(userDef.get);
sharedPropertyDefinition.set = userDef.set;
}
// 使用defineProperty定义
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) { // 如果dirty为true
watcher.evaluate();// 计算出新值,并将dirty 更新为false
}
// 如果依赖的值不发生变化,则返回上次计算的结果
return watcher.value
}
}
}
为什么使用缓存?
1.减少不必要的计算开销:计算属性的值是根据依赖的响应式数据计算而来的。如果每次访问计算属性都重新计算一次,无论依赖数据有没有变化,都会导致不必要的计算开销。
通过使用 dirty
属性,可以标记计算属性是否需要重新计算,从而避免不必要的计算。
2.提高性能与响应速度:通过缓存计算属性的值,当访问计算属性时,如果依赖数据没有发生改变,可以直接返回之前的缓存值,而不必重新计算。
这样可以提高性能并且快速响应用户的数据访问请求。
3.依赖数据发生变化时再重新计算:当依赖的响应式数据发生变化时,计算属性才需要重新计算。
通过将 dirty
属性设置为 true
,可以在下一次访问计算属性时触发重新计算,并将计算结果缓存起来。
1.3.watcher.evaluate()方法
evaluate() {
this.value = this.get()
this.dirty = false
}
update() {
if (this.lazy) {
this.dirty = true;
} else {
queueWatcher(this); //更新方法
}
}
当依赖的属性变化时,会通知watcher调用update方法,此时我们将dirty置换为true。这样再取值时会重新进行计算。
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) { // 计算属性在模板中使用 则存在Dep.target
watcher.depend()
}
return watcher.value
}
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}