AngularJS 的脏检查循环(digest loop)运行频率是多少?

34
讨论AngularJS的优点时,双向数据绑定经常被吹捧为Angular相比其他JS框架的主要优势。更深入地挖掘,文档表明这个过程是通过脏检查而不是通过事件驱动措施来完成的。起初,digest循环似乎是通过在周期间隔内在后台触发方法来完成的,在每个周期内检查所有的$watch。然而,进一步阅读后,似乎digest循环实际上是由rootScope.digest()触发的,而rootScope.digest()又由$.apply触发,$.apply又由事件(!)触发,例如通过ng-click调用的onClick事件。

但是,这怎么可能呢?我以为Angular 不使用变更监听器。那么digest循环真正的运行方式是什么呢?Angular是否会自动启动digest循环内部,还是由事件触发digest循环? 如果digest循环是自动运行的,它运行的频率是多少?


一些澄清点:

  • 我不是在询问手动绑定更改时digest循环如何运行。在这种情况下,如果您想强制进行digest循环,可以通过调用$.apply()来实现
  • 我也不是在询问用户事件触发时digest循环运行的频率。例如,如果ng-model在输入框上,当用户开始输入时,Angular将启动一个digest循环。令人困惑的是,为了知道用户正在输入,Angular不是在某个地方使用基于事件的onKeyUp吗?
  • 我已经知道每个digest循环最多有10个周期的限制。我的问题与每秒运行的digest循环数量有关,而不是每个digest循环的周期数。
  • 额外的问题:digest循环如何与JavaScript事件循环相关?JS事件循环是否定期在后台运行?digest循环是事件循环的同一事物,但只在“Angular上下文”中吗?这些是完全不同的概念吗?
3个回答

13

Angular digest会被触发,不是通过轮询来进行的。

代码执行后,Angular会触发一个digest。

例如:

 element.on('click', function() {
     $scope.$apply(function() { 
         // do some code here, after this, $digest cycle will be triggered
     });
 });

在编译/链接阶段之后,Angular 还将触发 $digest:

Compile > Link > Digest

至于触发多少个消化循环,这取决于作用域变量何时稳定。通常需要至少2个周期才能确定。


听起来似乎有两种事件监听器。 (1)用于在内存中对模型进行更改,这是 Backbone 和 KO 的操作方式。(2)针对基于用户操作的更改,触发摘要以执行脏检查,这是 Angular 的操作方式。对吗? - derekchen14
Angular有一种机制来检测内存中模型的变化。它循环遍历所有作用域变量,如果有任何变化,就调用相应的$watch监听器。当用户执行操作时,它会触发这个循环(即ng-click事件)。如果你在Angular中做任何事情,Angular通常会触发这个循环——不是因为任何东西必须改变,而只是以防万一有什么改变了。它经常这样做。你可能认为这是低效的,你是对的。 - pixelbits
例如,当您进行ajax $http调用并稍后返回时,Angular会触发$digest。 不是因为任何东西改变了或被触发了。 它无论如何都会执行。 如果发生任何更改,它将触发$watch监听器以更新视图。 这与其他框架的工作方式非常不同,其他框架中会发生某些事件直接触发事件侦听器。 在Angular中,它不关心是否有任何更改,它都会触发$digest周期。 - pixelbits

12

回答主要问题的简短直接的答案是“否”,Angular不会自动触发digest循环。

TL;DR 答案:

digest循环旨在在与Angular作用域实例关联的POJO模型上运行脏检查,因此仅需要在可能更改模型时运行。在运行在浏览器内的单页面Web应用程序中,以下操作/事件可能导致模型更改

  1. DOM事件
  2. XHR响应触发回调
  3. 浏览器位置更改
  4. 定时器(setTimout、setInterval)触发回调

相应地,Angular会在以下情况下触发digest循环

  1. 输入指令+ngModel、ngClick、ngMouseOver等
  2. $http和$resource
  3. $location
  4. $timeout

尝试从我的理解回答这些奖励问题:

  1. ngModel指令通常与angular输入指令(文本、选择等)一起使用,并且后者将侦听“change”事件并调用ngModelController公开的$setViewValue API以同步回dom值。在同步过程中,ngModelController将确保触发digest循环。
  2. digest循环与JS事件循环不同,后者是JS运行时的概念(请查看精美的可视化会话https://www.youtube.com/watch?v=8aGhZQkoFbQ),它针对事件队列运行并自动从队列中删除已使用的事件,但digest循环永远不会从其监视列表中删除监视器,除非您明确取消监视。
  3. 每秒digest循环的数量取决于通过循环执行的所有监视回调的效率。如果某些糟糕的代码需要一秒钟才能完成,则此digest循环将花费超过1秒的时间。

因此,避免angular性能陷阱的一些关键做法是:

  1. 监视回调应尽可能编写简单/高效的代码,例如将复杂的算法代码分离到工作者线程中
  • 如果一个监视器不再使用,请主动删除
  • 如果适用的话,优先调用$scope.$digest()而不是$scope.$apply()。$digest()仅运行部分作用域树,并确保子树下关联的模型反映到视图中。但是,$apply()将针对整个作用域树运行,它将遍历更多的监视器。

  • 4
    我认为发生的情况是这样的。AngularJS有一个聪明的假设,即模型更改仅在用户交互时发生。这些交互可能是由以下原因引起的:
    鼠标活动(移动、点击等)
    键盘活动(按键、松开等)
    AngularJS为相应事件的指令将表达式执行包装在$scope.$apply中,如@pixelbits在他的示例中所示。这会导致循环。
    还有一些其他事件也会触发AngularJS的digest循环。$timeout服务和$interval服务就是其中两个例子。在这些服务中包装的代码也会导致循环运行。
    可能还有其他事件/服务可以导致执行digest循环,但这些是主要的原因。
    这正是在Angular上下文之外对模型进行更改不会更新监视器和绑定的原因。因此,需要显式调用$scope.$apply。当与jQuery插件集成时,我们一直在这样做。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接