理解Angular中的$apply()以及$digest()
探索$apply()和$digest()
AngularJS提供了一个非常酷的特性叫做双向数据绑定(Two-way Data Binding),这个特性大大简化了我们的代码编写方式。数据绑定意味着当View中有任何数据发生了变化,那么这个变化也会自动地反馈到scope的数据上,也即意味着scope模型会自动地更新。类似地,当scope模型发生变化时,view中的数据也会更新到最新的值。
那么AngularJS是如何做到这一点的呢?
当你写下表达式如{{ aModel }}时,AngularJS在幕后会为你在scope模型上设置一个watcher,它用来在数据发生变化的时候更新view。这里的watcher和你会在AngularJS中设置的watcher是一样的:
1
2
3
|
$scope.$watch( 'aModel' , function (newValue, oldValue) { //update the DOM with newValue }); |
传入到$watch()中的第二个参数是一个回调函数,该函数在aModel的值发生变化的时候会被调用。当aModel发生变化的时候,这个回调函数会被调用来更新view这一点不难理解,但是,还存在一个很重要的问题!AngularJS是如何知道什么时候要调用这个回调函数呢?换句话说,AngularJS是如何知晓aModel发生了变化,才调用了对应的回调函数呢?它会周期性的运行一个函数来检查scope模型中的数据是否发生了变化吗?好吧,这就是$digest循环的用武之地了。
在$digest循环中,watchers会被触发。当一个watcher被触发时,AngularJS会检测scope模型,如果它发生了变化那么关联到该watcher的回调函数就会被调用。那么,下一个问题就是$digest循环是在什么时候以各种方式开始的?
在调用了$scope.$digest()后,$digest循环就开始了。假设你在一个ng-click指令对应的handler函数中更改了scope中的一条数据,此时AngularJS会自动地通过调用$digest()来触发一轮$digest循环。当$digest循环开始后,它会触发每个watcher。这些watchers会检查scope中的当前model值是否和上一次计算得到的model值不同。如果不同,那么对应的回调函数会被执行。调用该函数的结果,就是view中的表达式内容(译注:诸如{{ aModel }})会被更新。除了ng-click指令,还有一些其它的built-in指令以及服务来让你更改models(比如ng-model,$timeout等)和自动触发一次$digest循环。
什么时候手动调用$apply()方法?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
< body ng-app = "myApp" > < div ng-controller = "MessageController" > Delayed Message: {{message}} </ div > </ body > /* What happens without an $apply() */ angular.module('myApp',[]).controller('MessageController', function($scope) { $scope.getMessage = function() { setTimeout(function() { $scope.message = 'Fetched after 3 seconds'; console.log('message:'+$scope.message); }, 2000); } $scope.getMessage(); }); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* What happens with $apply */ angular.module( 'myApp' ,[]).controller( 'MessageController' , function ($scope) { $scope.getMessage = function () { setTimeout( function () { $scope.$apply( function () { //wrapped this within $apply $scope.message = 'Fetched after 3 seconds' ; console.log( 'message:' + $scope.message); }); }, 2000); } $scope.getMessage(); }); |
1
2
3
4
5
6
7
|
$scope.getMessage = function () { setTimeout( function () { $scope.message = 'Fetched after two seconds' ; console.log( 'message:' + $scope.message); $scope.$apply(); //this triggers a $digest }, 2000); }; |
$digest循环会运行多少次?
结语