Angularjs 为什么使用if(!$scope.$$phase)$scope.$apply()是反模式?
有时我需要在代码中使用Angularjs 为什么使用if(!$scope.$$phase)$scope.$apply()是反模式?,angularjs,angularjs-scope,Angularjs,Angularjs Scope,有时我需要在代码中使用$scope.$apply,有时它会抛出“摘要已在进行中”错误。所以我开始想办法解决这个问题,发现了这个问题:。但是,在评论中(以及在angular wiki上),您可以阅读: 如果(!$scope.$$phase)$scope.$apply(),则不要这样做,这意味着您的$scope.$apply()在调用堆栈中的位置不够高 现在我有两个问题: 为什么这是一种反模式 如何安全地使用$scope.$apply 防止“摘要已在进行”错误的另一个“解决方案”似乎是使用$time
$scope.$apply
,有时它会抛出“摘要已在进行中”错误。所以我开始想办法解决这个问题,发现了这个问题:。但是,在评论中(以及在angular wiki上),您可以阅读:
如果(!$scope.$$phase)$scope.$apply(),则不要这样做,这意味着您的$scope.$apply()在调用堆栈中的位置不够高
现在我有两个问题:
$timeout(function() {
//...
});
这是路吗?安全吗?因此,真正的问题是:我如何完全消除“摘要已在进行”错误的可能性
PS:我只使用$scope.$应用于非同步的非angularjs回调。(据我所知,在这些情况下,您必须使用$scope。$apply,如果您希望应用更改)
scope。$apply
触发一个$digest
循环,这是双向数据绑定的基础
$digest
循环检查对象,即连接到$scope
的模型(准确地说,$watch
),以评估其值是否已更改,如果检测到更改,则采取必要步骤更新视图
现在,当您使用$scope.$apply
时,会遇到一个错误“已在进行中”,因此很明显,$digest正在运行,但是什么触发了它
ans-->每调用一次$http
,所有ng单击、重复、显示、隐藏等都会触发一个$digest
循环,并在每个$SCOPE中运行最差的部分
比如说你的页面有4个控制器或指令A、B、C、D
如果每个属性中都有4个$scope
属性,那么页面上总共有16个$scope属性
如果在控制器D中触发$scope.$apply
,则$digest
循环将检查所有16个值!!!加上所有$rootScope属性
回答-->但$scope.$digest
在子级和同一作用域上触发$digest
,因此它将只检查4个属性。因此,如果您确定D中的更改不会影响A、B、C,那么请使用$scope.$diges
t not$scope.$apply
因此,即使用户未触发任何事件,只要点击ng或显示/隐藏ng,也可能会在100多个属性上触发
$digest
循环 在任何情况下,当您的摘要正在进行中,而您将另一个服务推送到摘要时,它只会给出一个错误,即摘要已经在进行中。
所以要解决这个问题,你有两个选择。
您可以检查正在进行的任何其他摘要,如轮询
第一个
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
$scope.$apply();
}
如果上述条件为真,则可以应用$scope。$apply otherwies不是,并且
第二种解决方案是使用$timeout
$timeout(function() {
//...
})
在$timeout完成它的执行之前,它不会让另一个摘要启动。在进一步挖掘之后,我能够解决使用
$scope是否总是安全的问题。$apply
。简而言之,答案是肯定的
长答覆:
由于浏览器执行Javascript的方式,两个摘要调用不可能偶然发生冲突
我们编写的JavaScript代码不是一次性运行的,而是轮流执行。每个回合从开始到结束都不间断地运行,当一个回合运行时,浏览器中不会发生其他任何事情。(来自)
因此,“摘要已在进行中”错误只能出现在一种情况下:在另一个$apply内发出$apply时,例如:
$scope.apply(function() {
// some code...
$scope.apply(function() { ... });
});
如果我们使用$scope.apply在纯非angularjs回调中,例如,setTimeout
的回调,则不会出现这种情况。因此,下面的代码是100%防弹的,无需执行if(!$scope.$$phase)$scope.$apply()
即使这一个也是安全的:
$scope.$apply(function () {
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
});
什么是不安全的(因为$timeout-像所有angularjs助手一样-已经为您调用了$scope.$apply
):
这也解释了为什么使用if(!$scope.$$phase)$scope.$apply()
是一种反模式。如果以正确的方式使用$scope.$apply
,您根本不需要它:例如,在纯js回调中,如setTimeout
阅读更多详细说明。使用
$timeout
,这是推荐的方式
我的情况是,我需要根据从WebSocket收到的数据更改页面上的项目。由于它位于Angular之外,没有$timeout,因此只会更改模型,而不会更改视图。因为Angular不知道数据已经更改了$timeout
基本上是告诉Angular在下一轮$digest中进行更改
我也尝试了以下方法,效果很好。对我来说,区别在于$timeout更清晰
setTimeout(function(){
$scope.$apply(function(){
// changes
});
},0)
现在,这无疑是一种反模式。我已经看到一个摘要爆炸,即使你检查$$阶段。您不应该访问由
$$
前缀表示的内部API
你应该使用
$scope.$evalAsync();
因为这是Angular ^1.4中的首选方法,并且专门作为应用层的API公开 我找到了非常酷的解决方案:
.factory('safeApply', [function($rootScope) {
return function($scope, fn) {
var phase = $scope.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn) {
$scope.$eval(fn);
}
} else {
if (fn) {
$scope.$apply(fn);
} else {
$scope.$apply();
}
}
}
}])
在需要的地方注入:
.controller('MyCtrl', ['$scope', 'safeApply',
function($scope, safeApply) {
safeApply($scope); // no function passed in
safeApply($scope, function() { // passing a function in
});
}
])
根据我的经验,您应该知道,如果您是从角度内还是角度外操作
范围
。因此,根据这一点,您总是知道是否需要调用scope.$apply
。如果你对角度/非角度范围操作使用相同的代码,那么你做得不对,它应该总是分开。。。因此,基本上,如果您遇到需要检查范围。$$phase
的情况,您的代码不是在co中设计的
.factory('safeApply', [function($rootScope) {
return function($scope, fn) {
var phase = $scope.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn) {
$scope.$eval(fn);
}
} else {
if (fn) {
$scope.$apply(fn);
} else {
$scope.$apply();
}
}
}
}])
.controller('MyCtrl', ['$scope', 'safeApply',
function($scope, safeApply) {
safeApply($scope); // no function passed in
safeApply($scope, function() { // passing a function in
});
}
])