背景
仔细阅读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.idif (!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.depIdsthis.depIds = this.newDepIdsthis.newDepIds = tmp//清除newDepIds,以备下次再用this.newDepIds.clear()//交换deps和newDeps的值//我们前面只是从dep中移除了watcher。watcher中还有这个dep。//我们也应该从watcher中移除这个不需要维护的dep。执行下面的//交换值操作即可。tmp = this.depsthis.deps = this.newDepsthis.newDeps = tmp//清除newDepIds,以备下次再用this.newDeps.length = 0}
