为什么使用if(!$ scope.$$ phase)$ scope.$ apply()反模式?

Dom*_*ann 91 angularjs angularjs-scope

有时我需要$scope.$apply在我的代码中使用,有时它会抛出"正在进行的摘要"错误.所以我开始找到解决这个问题的方法并找到了这个问题:AngularJS:在调用$ scope时预防错误$ digest.$ apply().但是在评论中(以及角度维基上),您可以阅读:

不要做(!$ scope.$$阶段)$ scope.$ apply(),这意味着你的$ scope.$ apply()在调用堆栈中不够高.

所以现在我有两个问题:

  1. 为什么这是一个反模式呢?
  2. 我怎样才能安全地使用$ scope.$ apply?

防止"正在进行摘要"错误的另一个"解决方案"似乎是使用$ timeout:

$timeout(function() {
  //...
});
Run Code Online (Sandbox Code Playgroud)

这是要走的路吗?它更安全吗?所以这是一个真正的问题:如何完全消除"摘要已经在进行中"错误的可能性?

PS:我只使用$ scope.$ apply适用于非同步的非angularjs回调.(据我所知,你必须使用$ scope.如果你想要应用你的更改,请申请$)

Dom*_*ann 110

经过一番挖掘后,我能够解决使用它是否总是安全的问题$scope.$apply.简短的回答是肯定的.

答案很长:

由于您的浏览器执行Javascript的方式,两个摘要调用不可能偶然发生冲突.

我们编写的JavaScript代码并不是一次性运行,而是依次执行.这些转弯中的每一个都从头到尾不间断地运行,当转弯运行时,我们的浏览器中没有其他任何事情发生.(来自http://jimhoskins.com/2012/12/17/angularjs-and-apply.html)

因此,错误"已经在进行中的摘要"只能在一种情况下发生:当在另一个$ apply内发出$ apply时,例如:

$scope.apply(function() {
  // some code...
  $scope.apply(function() { ... });
});
Run Code Online (Sandbox Code Playgroud)

如果我们在纯non-angularjs回调中使用$ scope.apply,例如回调函数,就不会出现这种情况.所以下面的代码是100%防弹,没有必要做setTimeoutif (!$scope.$$phase) $scope.$apply()

setTimeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);
Run Code Online (Sandbox Code Playgroud)

即使这个是安全的:

$scope.$apply(function () {
    setTimeout(function () {
        $scope.$apply(function () {
            $scope.message = "Timeout called!";
        });
    }, 2000);
});
Run Code Online (Sandbox Code Playgroud)

什么是不是安全的(因为$超时-像所有angularjs助手-已经呼吁$scope.$apply你):

$timeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);
Run Code Online (Sandbox Code Playgroud)

这也解释了为什么使用if (!$scope.$$phase) $scope.$apply()反模式.如果$scope.$apply以正确的方式使用,则根本不需要它:例如,在纯js回调中setTimeout.

请阅读http://jimhoskins.com/2012/12/17/angularjs-and-apply.html以获取更详细的说明.

  • `$ timeout`在语义上意味着在延迟后运行代码.它可能是一个功能安全的事情,但它是一个黑客.当你无法知道`$ digest`循环是否正在进行或者你已经在$ $ apply中时,应该有一种安全的方式来使用$ apply. (11认同)
  • 较新的方法是使用$ scope.$ evalAsync(),如果可能或在下一个周期中安全地执行当前的摘要周期.请参阅http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm (4认同)

Fla*_*ape 16

现在绝对是一种反模式.即使你检查了$$阶段,我也看到了消化的爆炸.您不应该访问由$$前缀表示的内部API .

你应该用

 $scope.$evalAsync();
Run Code Online (Sandbox Code Playgroud)

因为这是Angular ^ 1.4中的首选方法,并且特别是作为应用程序层的API公开.


Ris*_*tta 9

scope.$apply触发一个$digest循环,这是双向数据绑定的基础

$digest的对象周期的检查,即模型(准确地$watch)连接到$scope评估是否它们的值发生了变化,如果检测到的变化那么它采取必要的步骤来更新视图.

现在,当你使用时,$scope.$apply你面临一个错误"已经在进行中",所以很明显$ digest正在运行,但是什么引发了它?

ans - >每次$http通话,全部点击,重复,显示,隐藏等触发一个$digest周期和最糟糕的部分运行每个$ SCOPE.

即说你的页面有4个控制器或指令A,B,C,D

如果$scope每个属性中有4个属性,那么页面上总共有16个$ scope属性.

如果你$scope.$apply在控制器D中触发,那么一个$digest循环将检查所有16个值!加上所有$ rootScope属性.

答案 - >$scope.$digest触发一个$digest孩子和相同的范围,因此它只会检查4个属性.因此,如果您确定D中的更改不会影响A,B,C,那么请$scope.$diges不要使用$scope.$apply.

因此,$digest即使用户没有触发任何事件,仅仅ng-click或ng-show/hide可能会触发超过100个属性的循环!

  • 是的,我很遗憾地意识到这个项目的后期.如果我从一开始就知道这一点,就不会使用Angular.所有标准指令都触发$ scope.$ apply,它依次调用$ rootScope.$ digest,它对所有作用域执行脏检查.如果你问我,设计决定很差.我应该控制哪些范围应该进行脏检查,因为我知道数据是如何与这些范围相关联的! (2认同)

Lal*_*eva 8

在任何情况下,当您的摘要正在进行并且您推送其他服务进行摘要时,它只会给出错误,即摘要已在进行中.所以要治愈这个你有两个选择.您可以检查正在进行的任何其他摘要,如轮询.

第一

if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
    $scope.$apply();
}
Run Code Online (Sandbox Code Playgroud)

如果上述条件为真,则可以应用$ scope.$ apply otherwies not和

第二个解决方案是使用$ timeout

$timeout(function() {
  //...
})
Run Code Online (Sandbox Code Playgroud)

它不会让其他摘要开始直到$ timeout完成它的执行.