探索$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循环会运行多少次?
结语