AngularJS 1.x apply指令和digest指令初探

由问题引入

为实现双向数据绑定,AngularJS在scope model上会设置watcher,用于在数据发生变化时更新view。类似于:

1
2
3
$scope.$watch('aModel', function(newValue, oldValue) {
//update the DOM with newValue
});

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) 举例

setTimeout + $apply:plnkr演示

setTimout中改变了model的数据$scope.message,并在view层展示。

(1)不手动调用$apply():model有更新,但是view没被更新。

1
2
3
4
5
6
$scope.getMessage = function() {
setTimeout(function() {
$scope.message = 'Fetched after 3 seconds';
console.log('message:'+$scope.message);
}, 2000);
}

(2)手动调用$apply():model有更新,view也随之更新。

这里又区分为两种调用方式:wrapped function方式和无参调用方式。

  • wrapped function方式
1
2
3
4
5
6
7
8
9
$scope.getMessage = function() {
setTimeout(function() {
$scope.$apply(function() {
//wrap this within $apply
$scope.message = 'Fetched after 2 seconds';
console.log('message:' + $scope.message);
});
}, 2000);
}

将修改model的代码包裹在$apply()中,model修改时就能自动触发$rootScope.$digest(),从而触发watchers更新view.

  • 无参调用方式
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);
};

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没有变化。

分享
0%