上一篇提到的问题
上一篇提到了由于监听函数第三个传参是作用域本身,所以我们可以在监听函数内部改变作用域的值。但是这么做的话,会有另一个监控器来监控值的变化。但是在一次$digest中就无法检测值的变化。所以我们需要实现一种机制——$digest会持续遍历所有监听器,知道他们停止变化。
实现原理
我们将$digest改名为$$digestOnce。意思是每调用他一次就会遍历所有监听器一次。他会返回一个状态,表示这次遍历该数据是否发生变化。我们再用一个外层函数包裹$$digestOnce,一旦发现这个状态为true,就再执行一次$$$digestOnce。直到状态不再为true。这表示数据不再发生变化。
代码实现
|
|
重要提示
- 其实Angular的源码中并不存在$$digestOnce,而是将其直接封装在$digest中,这里只是为了结构的清晰。
- 通过这里我们可以看出,在一次$digest中,监听器可能会执行多次。
漏洞
假设我们有两个监听器,监听器a监听Scope.a的值并且他的监听函数会改变Scope.b的值。监听器b监听Scope.b的值并且他的监听函数会改变Scope.a的值。这样互相监听,最终会导致双方的值永远无法稳定,$digest会无限调用,这肯定是需要被杜绝的。
应对
对此,我们需要设定,当$digest执行一定次数以后,如果状态还是无法稳定,我们就宣布他的状态永远无法稳定,并抛出错误。为了性能考虑,Angular对这个次数的设定是10。
代码实现
|
|
基于值的检测与基于地址的检测
我们之前对比oldVal与newVal时,使用的是’===’操作符,这在检测基本值类型的数据时是完全没有问题的。当检测引用类型的值时比较的是引用的地址。但是有时候我们需要检测对象或数组内部数据的变更,这时候我们就需要基于值的检测,而不是基于地址的检测了。
由于基于值的检测时一项比较消耗性能(相对)的操作,所以我们应该提供一个可选参数,让使用者自己决定在什么时候开启(全等比较函数源码见下篇)。
代码实现
|
|
基于值的检测意味着如果数据时对象或数组类型,脏值查询会遍历数据的每一项,如果数据存在嵌套的数组或对象,还会递归的按值进行比较。
另一个问题
这里我们的深度检测只发生在newVal与oldVal比较的时候。回忆下,我们的新值会保存在watch.last中。试想如果保存的是一个引用类型的值,那么同一个值通过watchFn取得的和watch.last取得的其实是同一个地址。也就是说watch.fn(旧值)的变化会实时表现在watch.last(新值)中。这样就无法检测到引用类型值的变化了。
所以我们保存在watch.last中的数据应该是一个值,而不是一个地址。也就是说我们需要深度克隆一份引用类型的值。(源码分析见下一篇)
源码实现
|
|
最后一点小问题
在原生js中NaN!==NaN。如果我们的数据中包含NaN,在进行引用类型检测时(值类型的检测我们封装的$$areEqual已经帮我们处理了)由于NaN!==NaN那么这个值会始终是脏的。所以我们需要手动处理他。
源码实现
|
|