1.计算属性的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
function initComputed(vm) {
const computed = vm.$options.computed;

const watchers = (vm._computedWatchers = {}); //用来存放计算watcher

for (let k in computed) {
const userDef = computed[k]; //获取用户定义的计算属性
const getter = typeof userDef === "function" ? userDef : userDef.get; //创建计算属性watcher使用
// 创建计算watcher lazy设置为true
watchers[k] = new Watcher(vm, getter, () => {}, { lazy: true });
defineComputed(vm, k, userDef);
}
}

computed可以为函数也可以为对象,将lazy设置为true传给构造函数Watcher创建计算属性Watcher


2.对计算属性进行属性劫持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定义普通对象用来劫持计算属性
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: () => {},
set: () => {},
};

// 重新定义计算属性 对get和set劫持
function defineComputed(target, key, userDef) {
if (typeof userDef === "function") {
// 如果是一个函数 需要手动赋值到get上
sharedPropertyDefinition.get = createComputedGetter(key);
} else {
sharedPropertyDefinition.get = createComputedGetter(key);
sharedPropertyDefinition.set = userDef.set;
}
// 利用Object.defineProperty来对计算属性的get和set进行劫持
Object.defineProperty(target, key, sharedPropertyDefinition);
}

// 重写计算属性的get方法 来判断是否需要进行重新计算
function createComputedGetter(key) {
return function () {
const watcher = this._computedWatchers[key]; //获取对应的计算属性watcher
if (watcher) {
if (watcher.dirty) {
watcher.evaluate(); //计算属性取值的时候 如果是脏的 需要重新求值
}
return watcher.value;
}
};
}

defineComputed方法用于重新定义计算属性,主要是劫持get方法,需要根据依赖值是否发生变化来判断计算属性是否需要计算
createComputedGetter方法是判断计算属性依赖的值是否变化的核心,根据watcher添加dirty标志位,标志为true代表需要重新计算


3.Watcher改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
this.lazy = options.lazy; //标识计算属性watcher
this.dirty = this.lazy; //dirty可变 表示计算watcher是否需要重新计算 默认值是true
this.value = this.lazy ? undefined : this.get();
get() {
pushTarget(this); // 在调用方法之前先把当前watcher实例推到全局Dep.target上
const res = this.getter.call(this.vm); //计算属性在这里执行用户定义的get函数 访问计算属性的依赖项 从而把自身计算Watcher添加到依赖项dep里面收集起来
popTarget(); // 在调用方法之后把当前watcher实例从全局Dep.target移除
return res;
}
update() {
// 计算属性依赖的值发生变化 只需要把dirty置为true 下次访问到了重新计算
if (this.lazy) {
this.dirty = true;
} else {
// 每次watcher进行更新的时候 可以让他们先缓存起来 之后再一起调用
// 异步队列机制
queueWatcher(this);
}
}
// 计算属性重新进行计算 并且计算完成把dirty置为false
evaluate() {
this.value = this.get();
this.dirty = false;
}
depend() {
// 计算属性的watcher存储了依赖项的dep
let i = this.deps.length;
while (i--) {
this.deps[i].depend(); //调用依赖项的dep去收集渲染watcher
}
}

根据lazy属性判断是否computed,不是的话不去调用get进行依赖收集,computed的缓存通过update,在改变dirty状态后,下次访问计算属性才会重新计算,新增了evaluate方法用于重新计算,新增depend方法,让计算属性依赖值收集外层watcher


4.外层watcher的依赖收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createComputedGetter(key) {
if (Dep.target) {
watcher.depend()
}
}
// 默认Dep.target为null
Dep.target = null;
// 栈结构用来存watcher
const targetStack = [];

export function pushTarget(watcher) {
targetStack.push(watcher);
Dep.target = watcher; // Dep.target指向当前watcher
}
export function popTarget() {
targetStack.pop(); // 当前watcher出栈 拿到上一个watcher
Dep.target = targetStack[targetStack.length - 1];
}

在出入栈之后,计算属性会重新计算,然后将target更新,当外层的watcher更新target后,使用depend方法收集一遍外层的依赖,然后就可以计算并刷新视图。


5.小结

先通过设置对象get和set属性,进行一个劫持,然后改写watcher,根据dirty判断是否重新计算,调用depend收集外层渲染watcher的依赖,将watcher的dirty标识为true后,下次访问就重新计算属性,然后通过depend方法让计算属性依赖值收集外层watcher依赖,达到视图渲染效果。