由问题引入
为实现双向数据绑定,AngularJS在scope model上会设置watcher,用于在数据发生变化时更新view。类似于:
|
|
当aModel发生变化时,回调函数会被调用来更新view。
问题在于,AngularJS如何知道aModel的变化?
是否会周期性地运行一个函数来检查scope model是否发生了变化?
$digest循环,触发watchers,执行其关联的回调函数。
AngularJS更新view的流程
Angular自动 或 我们手动 调用$scope.$apply() -> 调用$rootScope.$digest(),触发一次$digest循环 -> 访问到所有的child scope中的watchers -> 关联的回调函数会改变view中的表达式内容。
1)Angular自动调用$scope.$apply()的情况:
Angular只负责对发生于AngularJS上下文环境中的变更会做出自动地响应,也即:使用built-in指令时。比如:将ng-click指令关联到一个button,并传入了一个function名到ng-click上。当该button被点击时,AngularJS会将此function包装到一个wrapping function中,然后传入到$scope.$apply()
2)手动调用$scope.$apply()的情况:
如果你在AngularJS上下文之外的任何地方(比如使用setTimeout的地方)修改了model,那么就需要通过手动调用$apply()来通知AngularJS,你修改了一些models,希望AngularJS帮你触发watchers来做出正确的响应。
3) 举例
在setTimout中改变了model的数据$scope.message,并在view层展示。
(1)不手动调用$apply():model有更新,但是view没被更新。
|
|
(2)手动调用$apply():model有更新,view也随之更新。
这里又区分为两种调用方式:wrapped function方式和无参调用方式。
- wrapped function方式
|
|
将修改model的代码包裹在$apply()中,model修改时就能自动触发$rootScope.$digest(),从而触发watchers更新view.
- 无参调用方式
|
|
PS: 推荐使用$timeout服务代替setTimeout(),前者会自动帮你调用$apply()。
- 两种方式的区别:
将function传入$scope.$apply()中,这个函数实际被包裹在try...catch代码块中,可以捕获异常。
4) $digest()循环的执行次数
$digest循环不会只运行一次。在当前的一次循环结束后,它会再执行一次循环用来检查是否有model发生了变化。这就是脏检查(Dirty Checking),它用来处理在listener函数被执行时可能引起的model变化。因此,$digest循环会持续运行直到model不再发生变化,或者$digest循环的次数达到了10次。因此,尽可能地不要在listener函数中修改model。
Note: $digest循环最少也会运行两次,即使在listener函数中并没有改变任何model。正如上面讨论的那样,它会多运行一次来确保model没有变化。