深入理解Angularjs 脏值检测(3)


Scope.prototype.$digest = function() {
 var self = this;
 _.forEach(this.$$watchers, function(watch) {
  var newValue = watch.watchFn(self);
  var oldValue = watch.last;
  if (newValue !== oldValue) {
   watch.listenerFn(newValue, oldValue, self);
  }
  watch.last = newValue;
 }); 
};

对每个监听器,我们调用监控函数,把作用域自身当作实参传递进去,然后比较这个返回值和上次返回值,如果不同,就调用监听函数。方便起见,我们把新旧值和作用域都当作参数传递给监听函数。最终,我们把监听器的last属性设置成新返回的值,下一次可以用它来作比较。

有了这个实现之后,我们就可以看到在$digest调用的时候,监听函数是怎么执行的:

http://jsbin.com/OsITIZu/3/embed?js,console

我们已经实现了Angular作用域的本质:添加监听器,在digest里运行它们。

也已经可以看到几个关于Angular作用域的重要性能特性:

  • 在作用域上添加数据本身并不会有性能折扣。如果没有监听器在监控某个属性,它在不在作用域上都无所谓。Angular并不会遍历作用域的属性,它遍历的是监听器。
  • $digest里会调用每个监控函数,因此,最好关注监听器的数量,还有每个独立的监控函数或者表达式的性能。

在Digest的时候获得提示

如果你想在每次Angular的作用域被digest的时候得到通知,可以利用每次digest的时候挨个执行监听器这个事情,只要注册一个没有监听函数的监听器就可以了。

想要支持这个用例,我们需要在$watch里面检测是否监控函数被省略了,如果是这样,用个空函数来代替它:

Scope.prototype.$watch = function(watchFn, listenerFn) {
 var watcher = {
  watchFn: watchFn,
  listenerFn: listenerFn || function() { }
 };
 this.$$watchers.push(watcher);
};

如果用了这个模式,需要记住,即使没有listenerFn,Angular也会寻找watchFn的返回值。如果返回了一个值,这个值会提交给脏检查。想要采用这个用法又想避免多余的事情,只要监控函数不返回任何值就行了。在这个例子里,监听器的值始终会是未定义的。

http://jsbin.com/OsITIZu/4/embed?js,console

这个实现的核心就这样,但是离最终的还是差太远了。比如说有个很典型的场景我们不能支持:监听函数自身也修改作用域上的属性。如果这个发生了,另外有个监听器在监控被修改的属性,有可能在同一个digest里面检测不到这个变动:

http://jsbin.com/eTIpUyE/2/embed?js,console

我们来修复这个问题。

当数据脏的时候持续Digest

我们需要改变一下digest,让它持续遍历所有监听器,直到监控的值停止变更。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/219.html