在阅读《AngularJS权威教程》时,第二章有两个例子有点奇怪,作者埋了一个不小的坑,却没有解释,于是我深入探讨之。结果我深刻理解了为什么js有setTimeout,angular还要封装$timeout,为什么有$apply,什么是$digest等等。
在AngularJS权威教程中,第8页和第9页,有这样两段JS代码
function MyController($scope, $timeout) {
var updateClock = function() {
$scope.clock = new Date();
$timeout(function() {
updateClock();
}, 1000);
};
updateClock();
};
和
function MyController($scope) {
$scope.clock = {
now: new Date()
};
var updateClock = function() {
$scope.clock.now = new Date()
};
setInterval(function() {
$scope.$apply(updateClock);
}, 1000);
updateClock();
};
在看第二段代码的时候,我发现很奇怪,为什么要用$scope.$apply呢?于是我把它去掉了,结果还真就不行了。但是我发现第一段代码是直接调用的updateClock,并没有用$scope.$apply啊?
带着这个疑问,我在网上找到了答案,其实这和angular的视图刷新机制有关。Angular的双向数据绑定是大家都知道的特性,其中一个方向就是$scope中的数据模型改变会被实时反映到视图上。但是这个过程是怎么实现的呢?
其实,当我们每一次写下类似{{aModel}}这样表达式的时候,我们创建了一个数据模型,angular会自动给它加上一个watcher去监听模型的变化,当aModel变化的时候,会触发监听函数,这是典型的观察者模式。
那么angular什么时候去察觉aModel的变化呢?答案是$digest,digest是消化的意思,也就是说,我们的数据模型如果发生变化,那么它就会变成需要被消化的东西。$digest会去遍历所有的数据模型,并将它们的变化一一消化。
我们从来没有调用过$scope.$digest(),同样angular也不会直接调用它。Angular会调用$scope.$apply(),这个方法会触发$rootScope.$digest(),然后开始$digest循环,就这样,从根作用域遍历所有的数据模型。
但是很多时候我们并不会手动调用$scope.$apply(),就像第一段代码一样,视图还是被更新了不是么?不过请注意,第一段代码是在$timeout中调用的updateClock,这是angular封装的函数,在这种angular上下文中,angularJS会将我们的代码wrap到一个function中并传入$apply(),所以不需要手动调用$apply。再看第二段代码,我们在setInterval中调用了updateClock,这并不是angular上下文,所以我们需要手动去调用一下$apply。
看到这里,我终于明白了为什么JS中有setInterval,angular还要去封装一个$timeout,为什么第二段代码要用$apply而第一段不需要。
知识就是这样一点一点被积累的,当它汇成大海的时候,我们就能在上面远航!
参考文献:
http://www.sitepoint.com/understanding-angulars-apply-digest/
转载请注明出处:http://www.channingbreeze.com/blog-4.html