AngularJS 无限Digest循环导致的错误(infdig)

栏目: 编程语言 · AngularJS · 发布时间: 6年前

内容简介:AngularJS 无限Digest循环导致的错误(infdig)

最近在开发Angular项目过程中遇到一个问题, 日期相关, 核心错误点在于我把一个日期对象挂到$scope 上, view 里面两个地方分别用到了当天和后一天, 大概的示例代码如下:

// test.ctrl.js
var app = angular.module('ericTestApp', ['ngRoute']).config(['$routeProvider', function ($routeProvider) {
  $routeProvider.when('/bad', {
    controller: 'BadCtrl', 
    template: '<div>today: {{today | date :"yyyy-MM-dd HH:mm:ss"}}</div><div>today: {{tomorrow() | date :"yyyy-MM-dd HH:mm:ss"}}</div>'
  }).otherwise({
    redirectTo: '/bad'
  });
}]);

app.controller('BadCtrl', ['$scope', function ($scope) {
  $scope.today = new Date();
  $scope.tomorrow = function () {
    console.log('tomorrow called ' + $scope.today);
    var tmr = $scope.today;
    tmr = tmr.setDate(tmr.getDate() + 1);
    return tmr;
  };
}]);

上面的代码运行起来会抛出 Error: $rootScope:infdig Infinite $digest Loop 的错误信息, 这问题涉及到两个点, 一是对象引用; 二是Angular 的$digest 循环和它的脏值检测.

我们重点来了解一下Angular 的$digest 循环, Angular 最突破性的特性就是他的双向绑定, 开始使用的时候确实会觉得非常爽, 但是他是如何来保证数据的同步更新的呢? 实际上使用了一个比较暴力的方式, 也就是每个view 中的expression去连续执行N遍, 如果所有的表达式值两次都一样, 那说明已经稳定(最后两次计算的值相等)了, 此时结束循环. 如果中间检测到某表达式的值两次计算有差异, 那就继续再都循环一次, 直到所有view 中表达式的值稳定. 那么问题来了, 如果一直不会稳定呢? 目前Angular 的处理是循环超过10次就报错.

看到这里, 应该很多人已经看出上面代码的问题了, 在tomorrow 函数中, 拿到了today 的引用加了一天以后重新赋给它, 这样导致每次循环在计算tomorow() 的值的时候, today 被改变了, 下次循环, 因为today被上次改了, tomorrow() 的返回值也随之改变, 所以会触发下次循环, 如此循环下去达到了Angular 容忍的 digest 循环最大次数(Angular 中称为digestTTL)而抛出错误.

上面说的问题我们可以用几个Demo 验证一下增强理解, Demo 代码见 http://jsbin.com/coxeru/edit?html,js,console,output :

抛出infdig error

// 绑定到view 的 scope 方法里面修改另一个scope 的值, 会造成无限循环.
app.controller('BadCtrl', function ($scope) {
  $scope.today = new Date();
  $scope.tomorrow = function () {
    console.log('tomorrow called ' + $scope.today);
    var tmr = $scope.today;
    tmr = tmr.setDate(tmr.getDate() + 1);
    return tmr;
  };
});

正确的做法, 绑定到$scope 的函数不去改变另外一个$ scope 的值

// 正常运行
app.controller('GoodCtrl', function ($scope) {
  $scope.today = new Date();
  $scope.tomorrow = function () {
    console.log('tomorrow called' + $scope.today);
    var tmrInMs = $scope.today.getTime();
    tmr = new Date(tmrInMs + (24 * 60 * 60 * 1000));
    return tmr;
  };
});
``` 
当然, Angular 的官网文档中也说明了几种常见导致无限digest 循环的错误, 在开发过程中也需要注意.

{% asset_img angular-doc-infdig.png %}

最后我们用一个Demo 演示了设置digstTTL的作用, 也方便进一步理解digest 循环

``` js
var digestTTL = 3; // 手动设置digestTTL
var app = angular.module('ericTestApp', ['ngRoute']).config(['$routeProvider', '$rootScopeProvider', function ($routeProvider, $rootScopeProvider) {
  $routeProvider.when('/deepTest', {
    controller: 'DeepTestCtrl', 
    template: '<div>today: {{today | date :"yyyy-MM-dd HH:mm:ss"}}</div><div>today: {{tomorrow() | date :"yyyy-MM-dd HH:mm:ss"}}</div>'
  }).otherwise({
    redirectTo: '/deepTest'
  });
  // 手动设置digestTTL
  $rootScopeProvider.digestTtl(digestTTL);
}]);

// 用于测试Angular Digest的次数 
var n = 1;
app.controller('DeepTestCtrl', function ($scope) {
  $scope.today = new Date();
  $scope.tomorrow = function () {
    var tmrInMs = $scope.today.getTime();
    console.log("digest " + n++);
    tmr = new Date(tmrInMs + (24 * 60 * 60 * 1000) * n);
    return tmr;
  };
});
AngularJS 无限Digest循环导致的错误(infdig)

我们看到返回结果打印4条日志, 且第4条的时候因为返回的值还未稳定, 直接抛出了错误. 这也验证了我们上面的说法. 因此在开发过程中改变$scope 上挂的东西的时候需要谨慎, 因为你不知道你写的这个方法什么时候被别人放到view 上去, 或者watch 起来. 当然理解了此错误的产生原因以后即使遇到也应该不难解决了.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

测试驱动开发

测试驱动开发

Kent Beck / 孙平平、张小龙 / 中国电力出版社 / 2004-4-1 / 28.00元

《测试驱动开发》(中文版)设想把编程看成是转动曲柄从井里提一桶水上来的过程。如果水桶比较小,那么仅需一个能自由转动的曲柄就可以了。如果水桶比较大而且装满水,那么还没等水桶全部被提上来你就会很累了。你需要一个防倒转的装置,以保证每转一次可以休息一会儿。水桶越重,防倒转的棘齿相距越近。测试驱动开发中的测试程序就是防倒转装置上的棘齿。一旦我们的某个测试程序能工作了,你就知道,它从现在开始并且以后永远都可......一起来看看 《测试驱动开发》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具