AngularJS 的变化检测

AngularJs 1.X的变化检测

双向绑定

AngularJs中引入了双向绑定机制

首先看一下模型和DOM进行双向绑定的代码片段

// view
{{eggs}}
// controller app.controller('getEggController', [ '$scope', function ($scope) { this._init = function () { $scope.eggs = 'myEgg'; }; this._init() }]);

在该代码片段中, controller中的模型变量$scope.eggs与模型中{{eggs}}双向绑定,当controller中的$scope.eggs发生变化时,模板中的{{eggs}}同事会变化,相应的模板中的{{eggs}}发生变化时,controller中的$scope.eggs也会变化。这种双向绑定的原理是什么呢? AngularJs框架是怎样实现这种双向绑定机制的呢?

原理:

AngularJs会为你再scope模型上设置一个watcher, 它用来在模型数据发生变化时更新view,这里用到的watcher和我们会在angularJs中设置的watcher是一样的。即:

$scope.$watch('eggs', function(newValue, oldValue) {  
  //update the DOM with newValue  
});  

关于$watch:
http://www.cnblogs.com/yg_zhang/p/4799369.html

$watch原理

传入到$watch()中的第二个参数是一个回调函数,该函数在eggs的值发生变化的时候会被调用。那么AngularJS是如何知道什么时候要调用这个回调函数呢?换句话说,AngularJS是如何知晓eggs发生了变化,才调用了对应的回调函数呢?
AngularJs会周期性的运行一个函数$digest检查scope模型中的数据是否发生了变化,如果模型发生了变化,那么$watch中的回调函数就会被调用。 但是实际上angularJs并没有直接调用$digest(), 而是调用$scope.apply()。$scope.apply()会调用$rootScope.$digest()。因此,一轮$digest循环在$rootScope开始,随后会访问到所有的children scope中的watchers。

$digest原理

当$digest()被调用时,angular会遍历当前scope,以及children scope上的所有$watch, 如果watched value发生了变化,则运行回调函数,如果watched value没有变化,则执行下一个watcher。

当一个$digest循环运行时,watchers会被执行来检查scope中的models是否发生了变化。如果发生了变化,那么相应的listener函数就会被执行。这涉及到一个重要的问题。如果listener函数本身会修改一个scope model呢?
答案是$digest循环不会只运行一次。在当前的一次循环结束后,它会再执行一次循环用来检查是否有models发生了变化。这就是脏检查(Dirty Checking),它用来处理在listener函数被执行时可能引起的model变化。因此,$digest循环会持续运行直到model不再发生变化,或者$digest循环的次数达到了10次。因此,尽可能地不要在listener函数中修改model。

Note:

$digest循环最少也会运行两次,即使在listener函数中并没有改变任何model。正如上面讨论的那样,它会多运行一次来确保models没有变化。

$digest会在什么情况被触发呢? 一般当发生了一下Angular Context的事件的时候会触发$digest, 比如:

  1. DOM Events(eg. ng-click etc)
  2. Ajax的回调 (eg. $http etc)
  3. Timer with callbacks(eg. $timeout etc)
  4. 直接调用 $apply, $digest.

而对于普通的非angular上下文的异步事件,比如onclick, setTimeout等则不会触发$digest.因此对于这些非angular上下文的异步事件,要想进行模型的更新,则需要手动调用$apply手动进行模型的更新.

AngularJS 的变化检测_第1张图片
image.png
$scop.apply()

$scope.$apply()会自动地调用$rootScope.$digest()。$apply()方法有两种形式。第一种会接受一个function作为参数,执行该function并且触发一轮$digest循环。第二种会不接受任何参数,只是触发一轮$digest循环。而在手动使用 的过程中,推荐使用第一种方式,讲一个函数包裹在$scope.$apply()中,因为这种方法会自动的添加try....catch语句进行错误处理。
比如,对于以下语句:

$scope.$apply(doSomething)

本质上等同于:

doSomething();
$scope.digest();

下面来看一个例子




    
    Document




    

I am here because I'm enabled

运行该代码,可以看到浏览器里面console打出的结果是 "You are now: enabled"。
但是你也许会疑惑,对于scope上的模型enabled明明进行了三次赋值,为什么只打出了一条log?
这是因为当$scope.enabled被第一次赋值初始化时,digest循环只会执行一次,若想要log语句三次都被执行,则要手动去调用$scope.apply()方法。 代码如下:




    
    Document




    

I am here because I'm enabled

这也就说明了,在controller里进行初始赋值时如果不手动调用$scope.apply(),对于$scope上的模型的赋值操作只有第一次才会生效。
补充: $apply 和 $digest的一些区别:
AngularJS 的变化检测_第2张图片
image.png

参考文章
https://www.thinkful.com/projects/understanding-the-digest-cycle-528/
http://blog.csdn.net/dm_vincent/article/details/38705099

你可能感兴趣的:(AngularJS 的变化检测)