在 Vue.js 中,计算属性是通过 Watcher 实例的lazy、dirty和value属性来实现自动更新和缓存的。在执行render函数时,会调用计算属性的getter方法,根据dirty值来决定是计算最新值还是返回缓存值。数据变化时,会依次触发计算属性的watcher和render函数的watcher,从而实现自动更新。这一机制提高了性能并确保计算属性的有效运作。
1# 初始化计算属性:initComputed
// 初始化计算属性,遍历组件实例上的computed配置
function initComputed(vm) {
const computed = vm.$options.computed;
// 创建一个对象用于保存每个计算属性对应的watcher实例
const watchers = (vm._computedWatchers = {});
// 遍历computed对象
for (let key in computed) {
let userDef = computed[key];
// 判断计算属性的定义方式,可以是函数形式或者包含get方法的对象形式
let fn = typeof userDef === 'function' ? userDef : userDef.get;
// 给每个计算属性创建一个Watcher实例,并设置为lazy模式,不会立即执行get函数
watchers[key] = new Watcher(vm, fn, { lazy: true });
// 对计算属性进行劫持,即定义getter和setter
defineComputed(vm, key, userDef);
}
}
2# 劫持计算属性:defineComputed
// 劫持计算属性,定义getter和setter方法
function defineComputed(target, key, userDef) {
// 获取计算属性的set方法,如果不存在则默认为空函数
const setter = userDef.set || (() => {});
// 使用Object.defineProperty定义计算属性的getter和setter
Object.defineProperty(target, key, {
// 定义getter,通过createComputedGetter函数创建
get: createComputedGetter(key),
// 设置setter
set: setter,
});
}
3# 访问计算属性:createComputedGetter
// 劫持计算属性的访问,返回getter函数
function createComputedGetter(key) {
return function () {
// 获取计算属性对应的watcher
const watcher = this._computedWatchers[key];
// 如果该计算属性需要重新计算(dirty为true),则执行evaluate方法重新求值
if (watcher.dirty) {
// 重新求值后,dirty变为false,下次就不会重新计算,而是返回缓存的值
watcher.evaluate();
}
// 当前计算属性watcher出栈后,如果还有渲染watcher或其他计算属性watcher,
// 让当前计算属性watcher订阅的dep也收集上一层的watcher(可能是计算属性watcher,也可能是渲染watcher)
if (Dep.target) {
watcher.depend();
}
// 返回watcher上的值作为计算属性的值
return watcher.value;
};
}
4# 计算属性监听器:Watcher
class Watcher {
constructor(vm, fn, options) {
this.vm = vm;
this.lazy = options.lazy; // lazy标识,用于判定是否为懒执行的计算属性watcher
this.dirty = this.lazy; // dirty标识,用于判定是否需要重新读取get返回值或者使用缓存值
this.value = this.lazy ? undefined : this.get(); // 存储get返回值
}
// 更新方法,用于触发计算属性的重新渲染
update() {
console.log('computed-watcher-update');
if (this.lazy) {
// 当计算属性依赖的值发生改变时,设置dirty标识为true,会在下次重新读取值时更新计算属性
this.dirty = true;
} else {
queueWatcher(this); // 将当前watcher加入异步渲染队列
this.get(); // 触发重新渲染
}
}
// 当计算属性watcher为dirty时,执行evaluate,并将dirty标识置为false
evaluate() {
console.log('computed-watcher-evaluate');
this.value = this.get(); // 重新获取用户函数的返回值
this.dirty = false; // 标识已经重新读取了值
}
// 计算属性可能存在嵌套的情况,此时需要计算属性watcher订阅的dep去收集上一层的watcher
// 该watcher可能是计算属性watcher,也可能是渲染watcher
depend() {
let i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
}
}