$apply()
并且$digest()
是AngularJS的两个核心,有时令人困惑的方面。要了解AngularJS如何工作,需要充分了解如何$apply()
和$digest()
工作。本文旨在解释$apply()
和$digest()
本来面目,以及他们如何能在你的一天到一天AngularJS编程有用的。
$apply
和$digest
探索
AngularJS提供了一个非常棒的功能,被称为双向数据绑定,大大简化了我们的生活。数据绑定意味着当您在视图中更改某些内容时,scope
模型将自动更新。类似地,只要scope
模型发生变化,视图就会以新值更新。AngularJS如何做?当您编写一个表达式({{aModel}}
)后,Angular会在scope
模型中设置一个观察者,随后模型更改时会更新该视图。这watcher
就像你在AngularJS设置任何观察者:
$scope.$watch('aModel', function(newValue, oldValue) {
//update the DOM with newValue
});
传递给的第二个参数$watch()
被称为侦听器函数,每当aModel
更改的值被调用。我们很容易理解,当aModel
调用此调用者的值时,更新HTML中的表达式。但是,还有一个大问题!Angular如何计算出什么时候调用这个监听器函数?换句话说,AngularJS如何知道何时aModel
改变它可以调用相应的侦听器?它是否定期运行功能以检查scope
模型的值是否已更改?那么这是$digest
循环的步骤。
这是$digest
观察者被解雇的周期。当观察者被触发时,AngularJS评估scope
模型,如果它已经改变,则调用相应的监听器函数。所以,我们的下一个问题是这个$digest
循环何时开始。
这个$digest
循环是由于调用而开始的$scope.$digest()
。假设您scope
通过ng-click
指令更改处理函数中的模型。在这种情况下,AngularJS $digest
通过调用自动触发一个循环$digest()
。当$digest
循环开始时,它会触发每个观察者。这些观察者检查scope
模型的当前值是否与上次计算的值不同。如果是,则执行相应的监听器功能。因此,如果您在视图中有任何表达式,它们将被更新。此外ng-click
,还有其他一些内置指令/服务,让你可以改变模型(例如ng-model
,$timeout
等),并自动触发一个$digest
周期。
到现在为止还挺好!但是,有一个小骗局。在上述情况下,Angular不直接调用$digest()
。相反,它呼叫$scope.$apply()
,反过来调用$rootScope.$digest()
。作为结果,一个摘要循环从此开始$rootScope
,随后访问了所有呼叫观察者的小范围。
现在,我们假设你附加一个ng-click
指令到一个按钮,并传递一个函数名称。单击按钮时,AngularJS会将函数调用包含在其中$scope.$apply()
。因此,您的功能按照惯例执行,更改模型(如果有),并且$digest
循环开始确保您的更改反映在视图中。
注意:$scope.$apply()
自动通话$rootScope.$digest()
。该$apply()
功能有两种口味。第一个将函数作为参数,对其进行评估,并触发一个$digest
循环。第二个版本没有任何参数,只是$digest
在调用时启动一个循环。我们会看到为什么前者是不久以前的首选办法。
$apply()
什么时候需要手动操作?
如果AngularJS通常将代码包装在$apply()
一个$digest
循环中,那么什么时候需要$apply()
手动执行调用?实际上,AngularJS有一件事很清楚。它将仅考虑在AngularJS上下文中完成的模型更改(即,将模型包装在内部的代码$apply()
)。Angular的内置指令已经执行此操作,因此您所做的任何模型更改都将反映在视图中。但是,如果您更改Angular上下文之外的任何模型,那么您需要通过$apply()
手动方式通知Angular的更改。这就像告诉Angular,你正在改变一些模型,它应该触发,watchers
以便你的改变正确地传播。
例如,如果您使用JavaScript的setTimeout()
功能更新scope
模型,Angular无法知道您可能会改变什么。在这种情况下,您有责任$apply()
手动拨号,这会触发一个$digest
周期。类似地,如果您有一个设置DOM事件监听器并更改处理函数内部的一些模型的指令,则需要调用$apply()
以确保更改生效。
我们来看一个例子。假设您有一个页面,一旦页面加载,您希望在延迟两秒后显示一条消息。您的实现可能看起来像以下列表中显示的JavaScript和HTML。
通过运行示例,您将看到延迟功能在两秒钟的间隔后运行,并更新scope
模型message
。仍然,视图不更新。原因,你可能已经猜到,我们忘了$apply()
手动打电话。因此,我们需要更新我们的getMessage()
功能,如下所示。
如果您运行此更新的示例,您可以在两秒钟后看到视图更新。唯一的改变是我们把我们的代码里面$scope.$apply()
自动地触发$rootScope.$digest()
。因此,观察者照常照常观看更新。
注:顺便问一下,你应该使用$timeout
服务,这是尽可能setTimeout()
使用自动$apply()
让你没有打电话给我们$apply()
手动。
另外请注意,在上述代码中,您可以像往常一样完成模型更改,并调用$apply()
(no-arg版本)到最后。看看下面的代码段:
$scope.getMessage = function() {
setTimeout(function() {
$scope.message = 'Fetched after two seconds';
console.log('message:' + $scope.message);
$scope.$apply(); //this triggers a $digest
}, 2000);
};
上面的代码使用no-arg版本$apply()
和工作。请记住,您应该始终使用该版本$apply()
接受函数参数。这是因为当你传递一个函数时$apply()
,函数调用被封装在一个try...catch
块中,并且发生的任何异常将被传递给$exceptionHandler
服务。
多少次的$digest
循环中运行?
当$digest
循环运行时,执行观察者以查看scope
模型是否已更改。如果它们有,则调用相应的侦听器函数。这导致一个重要的问题。如果监听器功能本身改变了scope
模型怎么办?AngularJS如何解释这种变化?
答案是$digest
循环不运行一次。在当前循环结束时,它将重新开始,以检查是否有任何模型已更改。这基本上是脏的检查,并且完成以考虑可能由监听器功能完成的任何模型更改。因此,$digest
循环保持循环,直到没有更多的模型更改,或者它的最大循环数为10。总是很好保持幂等,并尝试最小化监听器函数内的模型更改。
注意:至少,$digest
即使您的监听器功能不更改任何型号,也会运行两次。如上所述,它运行一次,以确保模型是稳定的,没有变化。
结论
我希望这篇文章澄清了什么$apply
和$digest
全部。要记住的最重要的一点是,Angular 能否检测到你的改变。如果不能,则必须$apply()
手动拨号。