angular核心$watch,$digest,$apply之间的联系

来源:互联网 发布:37诸神黄昏进阶数据 编辑:程序博客网 时间:2024/06/04 18:34

浏览器事件发生时,会在浏览器的上下文window中执行,而angular有自己的上下文angular content,angular 事件在自己的上下文angular content中执行。

$watch

$watch组成了双向绑定的一边,在指定的表达式改变时调用,每个作用域都维护了一个监视器列表,被称为$scope.$$watchers。每次你绑定一些东西到你的UI上时你就会往$watch队列里插入一条$watch。

<ul>  <li ng-repeat="person in people">      {{person.name}} - {{person.age}}  </li></ul>app.controller('MainCtrl', function($scope) {  $scope.people = [  {name: 'a', age: 21},  {name: 'b', age: 25},  {name: 'c', age: 22}  ];});
这个例子生成了多少个$watch呢?ng-repeat一个,每个person两个,一共(2*3)+1=7个。

除了在视图上绑定数据创建一个$watch,还可以自定义$watch,但是有几点需要注意。

<body ng-controller="MainCtrl">  <input ng-model="user.name" />  Name updated: {{updated}} times.</body>app.controller('MainCtrl', function($scope) {  $scope.user = { name: "Fox" };  $scope.updated = 0;  $scope.$watch('user', function(newValue, oldValue) {    if (newValue === oldValue) { return; }  //防止初始化时调用    $scope.updated++;  }, true); //第三个参数可选,如果设为true,则会比较对象的值,否则比较引用});
当controller执行到这个$watch时,它会立即执行一次,所以我们加入了判断来防止第一个初始化的调用。其次是第三个参数,如果省略或者为false,则以上例子输入框的值改变之后,update的值不会改变,因为比较的user的引用。

每一个绑定到了UI上的数据都会生成一个$watch,那这些$watch是什么时候生成的呢?当我们的模版加载完毕时,也就是在linking阶段(Angular分为compile阶段和linking阶段),Angular解释器会寻找每个directive,然后生成每个需要的$watch。

$digest

$digest是将$watch和$apply绑定在一起的魔法胶水函数。从高级别来看,$digest将计算作用域(及作用域的孩子)中所有的$watch表达式。并在任何一个发生改变时调用监视器回调。但是监视器也可以改变作用域,所以其他监视器需要得到这个通知,因此$digest发生在一个循环中,如下面的伪代码所示:
var dirty = true;var iterations = 0;while (dirty && iterations++ < TIMES_TO_LOOP) {dirty = false;for (var i = 0; i < scope.watchers.length(); i++) {var currentValue = scope.watchers[i].get();if (currentValue != scope.watchers[i].oldValue) {dirty = ture; //如果有数据改变了,则又$digest一次,直到没有数据改变scope.watchers[i].callback(currentValue, scope.watchers[i].oldValue);scope.watchers[i].oldValue = currentValue;}}}
angular将TIMES_TO_LOOP(TTL)默认设为10,即$digest最多循环10次,超出后将抛出一个错误,防止无限循环,我们可以通过$rootScope服务和digestTtl函数改变这个值。
var app = angular.module('MyApp', [], function ($rootScopeProvider) {$rootScopeProvider.digestTtl(15); //将TTL改为15});
angular的内置指令会默认调用$digest,还有一点需要注意的是,UI中的所有指令都将创建至少一个监视器,所以angular的性能问题几乎总是因为以不合理的方式使用ngRepeat指令所引起的。如以上第一段代码,如果person对象达到10000个以上就会严重影响性能,实际上angular建议单个页面中不要使用超过2000个监视器。

除了默认调用之外,有时候我们也可以手动调用$digest循环,记住只有angular事件,也即是angular content处理的事件才会调用$digest循环,如果在window环境处理的事件,比如setTimeout,Jquery事件,则不会调用$digest循环,如果我们不手动调用,则视图不会随模型数据变化更新。
angular.module('app', function ($scope) {setTimeout(function () {$scope.arr = [1];$scope.$digest(); //setTimeout中手动调用$digest});});

$apply

$appy组成了双向数据绑定的另一边,它将通知angular某些东西改变了,$watch表达式的值应该重新计算。实际上$apply总是会默认调用$digest循环。它的官方伪代码如下:
function $apply(expr) {    try {        return$eval(expr);    } catch(e) {        $exceptionHandler(e);    } finally {        $root.$digest();    }}
事件发生时是谁在决定事件是在window中执行还是angular content执行?答案是$apply(),例如内置的ngClick指令会默认调用$apply。那么什么时候需要我们手动调用$apply呢?和$digest一样,在angular里面执行angular框架之外的表达式,比如说:浏览器DOM事件、setTimeout、XHR或其他第三方的库时,这时就要求我们手动调用$apply()。

我们可以把函数传入$apply中,也可以在外部单独调用,但是有一些区别。
element.bind('click', function() {  scope.foo++;  scope.bar++;  scope.$apply(); //我们是在angular context的外面更新的数据,    //如果有发生错误,Angular永远不知道});element.bind('click', function() {  scope.$apply(function() {  //把它封装进$apply里面,错误会传到angular里      scope.foo++;          //例如一个服务器获取数据失败      scope.bar++;  });})


 









阅读全文
0 0