AngularJS的加载原理:
AngularJS的模板加载分为编译(compile)和链接(linking)两个阶段,在linking阶段,AngularJS解释器会寻找每个directive,然后生成每个需要的$watch。对了,$watch就是在这个阶段生成的。
接下来,开始用到 $digest了
$digest 循环
从字面上看,digest是 “消化”的意思,总感觉这个名字怪怪的,跟不可思议的是 dirty-checking, 字面意思“脏检查”,还是不翻译为好。原作者的本意肯定不是这个意思,只可意会不可言传!
$digest 是一个循环,它在循环做什么呢? $digest 在遍历我们的$watch。 $digest 一个个地询问$watch —— “嗨,你观察的数据发生变化了没?”
这个遍历就是所谓的dirty-checking。既然所有的$watch都检查完了,那就要问了:有没有$watch更新过?如果有至少一个更新过,这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。记住如果循环超过10次的话,它将会抛出一个异常,以免出现无限循环。 当$digest循环结束时,DOM相应地变化。
看段代码,例如: controllers.js
app.controller('MainCtrl', function() { $scope.name = "Foo"; $scope.changeFoo = function() { $scope.name = "Bar"; } });
对应的html文件,index.html
{{ name }} <button ng-click="changeFoo()">Change the name</button>
这里只有一个$watch,因为ng-click不生成$watch(函数是不会变的)。
$digest 执行的流程是:
- 在浏览器按下按钮;
- 浏览器接收到一个事件,进入angular context。
- $digest循环开始执行,查询每个$watch是否变化。
- 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。
- 新的$digest循环没有检测到变化,此时浏览器拿回控制权,更新与$scope.name新值相应部分的DOM。
从中可以看出AngularJS的一个明显的不足:每一个进入angular context的事件都会执行一个$digest循环,哪怕仅仅是输入一个字母,$digest 都会遍历整个页面的所有$watch。
$apply 的应用
Angular context 是整个Angular的上下文,也可以把它理解为Angular容器,那么,是谁来决定哪些事件可以进入 Angular Context,哪些事件又不能进入呢? 其控制器在 $apply手上。
如果当事件触发时,调用$apply,它会进入angular context,如果没有调用就不会进入。你可能会问:刚才的例子并没有调用$apply,这是怎么回事呢?原来,是Angular背后替你做了。当点击带有ng-click的元素时,事件就会被封装到一个$apply调用中。如果有一个ng-model="foo"的输入框,当输入一个字母 f 时,事件就会这样调用,$apply("foo = 'f';")。