依赖收集
我们调用defineReactive
给响应式属性添加了get
特性,get
函数将在该属性被访问时调用并将返回作为属性值。
get
特性函数执行时,第一步是先计算出响应式属性的值。之后,就是收集依赖的过程。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// ...
const dep = new Dep()
const getter = property && property.get
// 递归地对 val 进行响应式处理,并返回 val 对应的 __ob__
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 每次获取当前属性值时,都要收集订阅者、
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 1、依赖收集:
// - 该属性值的闭包 dep 将当前 Dep.target 作为订阅者
// - 当前 Dep.target 将该属性值的闭包 dep 作为依赖
// 以便该属性值自身变化时,通知订阅者
dep.depend()
if (childOb) {
// 2、子属性的依赖收集(仅当该属性值为对象时):
// - 该属性值对应的观察对象的属性 dep 将当前 Dep.target 作为订阅者
// - 当前 Dep.target 将该属性值对应的观察对象的属性 dep 作为依赖
// 以便该属性值动态增加/删除 属性/元素 的时候通知 watcher
childOb.dep.depend()
if (Array.isArray(value)) {
// 3、若该属性值是数组,还需递归针对数组每个元素进行子属性的依赖收集
dependArray(value)
}
}
}
return value
}
}
}
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
34
35
36
37
38
39
40
41
42
属性的依赖收集
我们知道,在 Watcher 计算其表达式时,会将当前Dep.target
设置为该Watcher
。若是 Watcher 在计算表达式的过程中访问了响应式属性,那么就会在此时做依赖收集的工作。
dep
是响应式属性的闭包dep
,调用dep.depend()
,进而调用了Dep.target.addDep()
方法将dep
添加到了Dep.target
的newDeps
里,这样dep
就成为了Dep.target
这个 Watcher 的依赖了。与此同时,dep
也会将Dep.target
这个 Watcher 添加到dep.subs
,这样Dep.target
就成为了dep
的订阅者了。
// src/core/observer/dep.js
export default class Dep {
// ...
addSub (sub: Watcher) {
this.subs.push(sub)
}
// ...
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/core/observer/watcher.js
export default class Watcher {
// ...
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 若是该 watcher 之前没有过该 dep,则将 watcher 添加到 dep.subs(订阅者) 里
dep.addSub(this)
}
}
}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
属性的值的依赖收集
需要注意的是,此处的闭包dep
所关联的是响应式属性自身,也就意味着只有当属性值整个被替换时,才会去通知订阅者。但若是属性值是引用类型比如对象,给对象添加/删除属性,对象的引用并没有改变,此时无法触发dep.notify()
来通知订阅者,那该怎么办呢?(可先阅读下一节通知更新
,再回来阅读下面的内容)
let childOb = !shallow && observe(val)
注意到,我们在给响应式属性添加get
和set
之前,执行了上面这一句,而这就是响应式属性值处理为响应式对象(若属性值是对象或数组的话)并返回了属性值的观察者对象childOb
,而观察者对象的childOb.dep
也是跟闭包dep
一样的依赖对象。紧接着,调用childOb.dep.depend
将当前Dep.target
与childOb.dep
关联起来,Dep.target
成为了childOb.dep
的订阅者,childOb.dep
也成为了Dep.target
的依赖。若响应式属性的值为数组,还会调用dependArray(value)
以遍历数组每个元素来收集依赖。最终在响应式属性值内部的子属性或元素发生变化时,也能通知到订阅者了(详情请见通知更新
)。
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
2
3
4
5
6
7
8
9
总结
Watcher 在计算表达式的值的时候,会将响应式属性的闭包dep
作为依赖。若响应式属性的值是引用类型,还会将响应式属性的值对应的观察者对象的dep
作为依赖。这样的话,无论是响应式属性改变,还是响应式属性值的子元素/子属性改变,都能调用不同的dep.notify
通知到订阅者进行更新。