通过脏检查$scope对象
Angular在$scope对象中维护一个简单的观察者数组。如果您检查任何$scope,就会发现它包含一个名为$$watcher的数组。
每个观察者都是一个对象,其中包含其他内容
观察程序正在监视的表达式。这可能只是一个属性名称,或者更复杂的东西。表达式的最后一个已知值。这可以根据表达式的当前计算值进行检查。如果值不同,观察者将触发函数并将$scope标记为dirty。如果观察程序是脏的,将执行的函数。
如何定义观察者
在AngularJS中有许多不同的定义观察者的方法。
您可以在$scope上显式地$watch属性。$范围$watch('person.username',validateUnique);您可以在模板中放置{{}}插值(将在当前$scope上为您创建一个观察程序)。<p>用户名:{{person.username}}</p>您可以要求一个指令(如ng模型)为您定义观察者。<input ng model=“person.username”/>
$digest周期检查所有观察者的最后值
当我们通过正常通道(ng模型、ng重复等)与AngularJS交互时,指令将触发摘要循环。
摘要循环是对$scope及其所有子级的深度优先遍历。对于每个$scope对象,我们迭代其$$watcher数组并计算所有表达式。如果新的表达式值与上一个已知值不同,则调用观察者的函数。这个函数可能会重新编译DOM的一部分,重新计算$scope上的值,触发AJAX请求,任何您需要它做的事情。
遍历每个作用域,并根据最后一个值计算和检查每个监视表达式。
如果触发了观察者,则$scope是脏的
如果触发了观察者,则应用程序知道发生了变化,$scope被标记为dirty。
观察者函数可以更改$scope或父$scope上的其他属性。如果触发了一个$watcher函数,我们不能保证我们的其他$scope仍然是干净的,因此我们再次执行整个摘要循环。
这是因为AngularJS具有双向绑定,因此可以将数据传递回$scope树。我们可能会更改已经消化的较高$scope的值。也许我们更改了$rootScope上的值。
如果$digest是脏的,我们将再次执行整个$digest循环
我们不断循环$digest循环,直到消化循环干净(所有$watch表达式的值与上一个循环中的值相同),或者达到消化极限。默认情况下,此限制设置为10。
如果我们达到摘要限制,AngularJS将在控制台中引发错误:
10 $digest() iterations reached. Aborting!
摘要对机器来说很难,但对开发人员来说很容易
正如您所看到的,每当AngularJS应用程序发生变化时,AngularJS都会检查$scope层次结构中的每个观察者,以查看如何响应。对于开发人员来说,这是一个巨大的生产力优势,因为您现在几乎不需要编写任何布线代码,AngularJS只会注意到值是否发生了变化,并使应用程序的其余部分与变化保持一致。
从机器的角度来看,这是非常低效的,如果我们创建了太多的观察者,会减慢我们的应用程序的速度。Misko引用了大约4000名观察者的数据,在你的应用程序在较旧的浏览器上运行缓慢之前。
例如,如果在大型JSON数组上重复,则很容易达到此限制。您可以使用一次性绑定等功能来编译模板,而无需创建观察者。
如何避免创建过多观察者
每次用户与应用程序交互时,应用程序中的每个观察者都将至少进行一次评估。优化AngularJS应用程序的很大一部分是减少$scope树中的观察者数量。一种简单的方法是一次性绑定。
如果您有很少更改的数据,则只能使用::语法将其绑定一次,如下所示:
<p>{{::person.username}}</p>
or
<p ng-bind="::person.username"></p>
只有当呈现包含模板并将数据加载到$scope中时,才会触发绑定。
当你重复很多项目时,这一点尤为重要。
<div ng-repeat="person in people track by username">
{{::person.username}}
</div>