比较引起消化的方法

时间:2014-11-03 07:54:59

标签: javascript angularjs performance angularjs-scope

我很久以前就看过这篇文章: https://coderwall.com/p/ngisma
它描述了一种方法,如果我们不处于申请或摘要阶段,则触发$ apply。

$scope.safeApply = function(fn) {
  var phase = this.$root.$$phase;
  if(phase == '$apply' || phase == '$digest') {
    if(fn && (typeof(fn) === 'function')) {
      fn();
    }
  } else {
    this.$apply(fn);
  }
};

Angular有$scope.$evalAsync方法(取自1.2.14):

   $evalAsync: function(expr) {
            // if we are outside of an $digest loop and this is the first time we are scheduling async
            // task also schedule async auto-flush
            if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
              $browser.defer(function() {
                if ($rootScope.$$asyncQueue.length) {
                  $rootScope.$digest();
                }
              });
            }

            this.$$asyncQueue.push({scope: this, expression: expr});
          }

如果我们不处于阶段,则调用digest并将当前调用添加到asyncQueue。

还有$ apply,$ digest和$ timeout方法。 令人困惑。
触发摘要周期(脏检查和数据绑定)所提到的所有方法之间有什么区别? 每种方法的用例是什么?
safeApply()还安全吗? :)
我们有什么替代方案而不是safeApply()(如果我们在摘要周期中调用$ apply)?

2 个答案:

答案 0 :(得分:2)

作为一个高级别的介绍我会说很少需要实际启动你自己的摘要周期,因为角度处理大多数情况。

话虽如此,让我们深入研究这个问题。

作为一个非常高的级别,$ digest循环如下所示:

Do:
- - - If asyncQueue.length, flush asyncQueue.
- - - Trigger all $watch handlers.
- - - Check for "too many" $digest iterations.
While: ( Dirty data || asyncQueue.length )

所以基本上$evalAsync正在将函数添加到asyncQueue并在需要时推迟摘要。但是,如果它已经处于摘要周期,它将刷新asyncQueue,它只会调用该函数。

您可能会注意到非常safeApply类似。一个不同之处在于,不是将函数添加到asyncQueue,而是仅调用它,这可能发生在例如循环的中间。另一个区别是它暴露并依赖于$$变量,它们是内部的。

$evalAsync $apply$digest之间最重要的差异(我将在下面$timeout$evalAsync和{{1} }在$apply处启动摘要,但您在任何范围内调用$rootScope。如果您认为需要这种灵活性,则需要评估您的个案。

使用$digest非常类似于$timeout,除非它总是将其推迟到当前摘要周期(如果有的话)。

$evalAsync

如果你已经在摘要周期中给你

$timeout(function() {
    console.log( "$timeout 1" );
});
$scope.$evalAsync(function( $scope ) {
    console.log( "$evalAsync" );
});

即使它们以相反的顺序被调用,因为超时一被延迟到它实例化的下一个摘要周期。

编辑对于评论中的问题。 据我所知,$evalAsync $timeout 1 $apply之间的最大区别是$evalAsync实际上是在触发$apply周期。对您而言,这意味着您需要确保在未进入摘要周期时致电$digest。只是为了透明,以下代码也是有效的:

$apply

将调用apply函数,将no-op函数添加到$scope.$apply(function() { $scope.$evalAsync(function() { }); }); 并开始摘要循环。文档说,建议每当模型发生变化时都使用$asyncQueue,这可能发生在$apply

$evalAsyncfn(); $scope.apply()之间的区别仅在于$scope.apply(fn)为函数执行try catch并明确使用$scope.apply(fn)。此外,您实际上可以尝试在$exceptionHandler中导致摘要周期,这将改变处理申请的方式。

我还想在这个时候指出1.3中的更复杂,假设它保持这种状态。将有一个名为fn的函数用于推迟应用调用(reference)。

这篇文章汇编了This blogso post的一些信息以及我的一些经验。

希望这有帮助!

答案 1 :(得分:1)

只有一种方法可以启动摘要周期:调用$digest。它很少直接调用。

$apply本质上是$digest的安全包装器。安全意味着它处理错误。因此,大多数情况下,这是您要调用以传播更改的方法。另一个重要的区别是它总是在根范围内开始一个摘要周期,而$digest可以在任何范围内调用,只影响该范围及其后代。

许多服务和指令通常通过调用$apply来触发摘要周期。唯一需要知道的是,你必须自己调用$apply或者已经被service / directive调用了。

  

safeApply仍然安全吗?

是的,因为仍然只有一种方法可以启动摘要周期。

  

我们有什么替代方案而不是safeApply()(如果我们在摘要周期中调用$ apply)?

在摘要周期中不调用$apply。认真。处于这种情况几乎总是一个设计问题。最好解决这个问题,而不是覆盖它。

无论如何,解决方案使用的方法,检查$$phase,是了解摘要周期是否正在运行的唯一方法。如你所示,它甚至在内部被Angular使用。做if (!$rootScope.$$phase)就足够了。但请记住,这可能会破坏新版本的Angular。顺便说一句:正如作者提出的那样,修补最上面的范围会使解决方案对孤立的范围无用。

对于刚开始使用Angular的人来说,Angular的数据绑定方式确实很难理解。但基本上所有这一切归结为:对于要识别/处理的每​​个更改,有人必须致电$apply(或$digest。而已。这应该由指令和服务完成。如果你做的一切正确的话,实际上很难得到'$ apply in progress'错误。