背景
仔细阅读Vuejs
的Watcher.js
,发现他不仅维护了一个deps
以及和他相关的depIds
。还维护了一个newDeps
和newDepIds
。为什么要维护两个dep
数组呢?
答疑
这其实是为了移除掉那些不需要观察的数据,也就是说这个watcher
不需要再关心某些数据是否变动了,这样说可能抽象,举个例子:
computed: {
value() {
//假设this.sign一开始为true.
if(this.sign) {
return this.list
} else {
return this.anotherList
}
}
我定义了一个computed
,那么它对应的watcher
实例,应该观察哪些数据呢,或者说应该订阅哪些数据呢?
首先思考他读取了什么:首先读取了**sign**
,发现**sign**
是**true**
的情况下,那么会读取**list**
. 所以这个**watcher**
实例应该进入**sign**
和**list**
属性对应的**dep**
实例。也就是此时**watcher**
实例的**deps**
中也应该是这两个**dep**
组成的数组。
他会进入**anotherList**
吗?答案是否定的,因为并没有读取那个数据。
做一个假设:我将this.sign
改成false
。此时肯定会触发Watcher
实例的update
操作,就会导致重新执行get
操作:
:::info
这次,他又会读取**sign**
,接着读取**anotherList**
,导致**watcher**
实例应该又多了一个成员:**anotherList**
对应的dep。
:::
细想一下,**watcher**
实例还需要观察**list**
数据,当然不需要。不管**list**
怎么改变,这个**computed**
的结果根本不会收到任何影响,所以这个**Watcher**
实例不应该观察这个**list**
数据了:**list**
对应的**dep**
应该将这个**watcher**
移除,而**watcher**
实例中的**deps**
也应该将**list**
对应的**dep**
移除。**newDepIds**
和**newDeps**
就是用来完成这件事的。
newDepIds和newDeps完成的任务
实际上,**newDeps**
和**newDepIds**
就是这次**get**
执行收集到的依赖。
而,**deps**
和**depIds**
是上次**get执行收集到的依赖。**
对应上面的🌰那就是:
//伪代码
deps = [’sign‘, 'list'];
newDeps = ['sign', 'anotherList'];
还是看源码,deps
和newDeps
在源码中是如何被使用的:
这是在添加依赖时:
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
//当newDepIds中不存在这个dep时,才收集该dep。
this.newDepIds.add(id)
this.newDeps.push(dep)
//如果depIds存在这个dep。说明这个dep已经收集了这个watcher了
//就没必要反复收集了。
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
这是cleanupDeps
方法,在get
方法收集完依赖之后,这个时候需要做一次依赖的清理,使得已经不需要观察的数据取消观察:
cleanupDeps () {
let i = this.deps.length
//遍历原来收集过的依赖
while (i--) {
const dep = this.deps[i]
//不存在于newDepIds中,说明都是被取消观察的数据。
if (!this.newDepIds.has(dep.id)) {
//针对这些依赖,执行removeSub,从而将这个watcher
//移除出依赖列表。
dep.removeSub(this)
}
}
//交换depIds和newDepIds的值
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
//清除newDepIds,以备下次再用
this.newDepIds.clear()
//交换deps和newDeps的值
//我们前面只是从dep中移除了watcher。watcher中还有这个dep。
//我们也应该从watcher中移除这个不需要维护的dep。执行下面的
//交换值操作即可。
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
//清除newDepIds,以备下次再用
this.newDeps.length = 0
}