在阅读《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/