就像相等检测一样,Angular也内置了自己的深拷贝函数,但我们还是用Lo-Dash提供的。我们修改一下$digestOnce,在内部使用新的$$areEqual函数,如果需要的话,也复制最后一次的引用:
Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; };
现在我们可以看到两种脏检测方式的差异:
http://jsbin.com/ARiWENO/3/embed?js,console
相比检查引用,检查值的方式显然是一个更为复杂的操作。遍历嵌套的数据结构很花时间,保持深拷贝的数据也占用不少内存。这就是Angular默认不使用基于值的脏检测的原因,用户需要显式设置这个标记去打开它。
Angular也提供了第三种脏检测的方法:集合监控。就像基于值的检测,也能提示对象和数组中的变更。但不同于基于值的检测方式,它做的是一个比较浅的检测,并不递归进入到深层去,所以它比基于值的检测效率更高。集合检测是通过“$watchCollection”函数来使用的,在这个系列的后续部分,我们会来看看它是如何实现的。
在我们完成值的比对之前,还有些JavaScript怪事要处理一下。
非数字(NaN)
在JavaScript里,NaN(Not-a-Number)并不等于自身,这个听起来有点怪,但确实就这样。如果我们在脏检测函数里不显式处理NaN,一个值为NaN的监听器会一直是脏的。
对于基于值的脏检测来说,这个事情已经被Lo-Dash的isEqual函数处理掉了。对于基于引用的脏检测来说,我们需要自己处理。来修改一下$$areEqual函数的代码:
Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } };
现在有NaN的监听器也正常了:
http://jsbin.com/ijINaRA/2/embed?js,console
基于值的检测实现好了,现在我们该把注意力集中到应用程序代码如何跟作用域打交道上了。
$eval - 在作用域的上下文上执行代码
在Angular中,有几种方式可以在作用域的上下文上执行代码,最简单的一种就是$eval。它使用一个函数作参数,所做的事情是立即执行这个传入的函数,并且把作用域自身当作参数传递给它,返回的是这个函数的返回值。$eval也可以有第二个参数,它所做的仅仅是把这个参数传递给这个函数。