如何在 AngularJS 中使用 $scope.$watch 和 $scope.$apply?
Posted
技术标签:
【中文标题】如何在 AngularJS 中使用 $scope.$watch 和 $scope.$apply?【英文标题】:How do I use $scope.$watch and $scope.$apply in AngularJS? 【发布时间】:2013-02-13 06:56:26 【问题描述】:我不明白如何使用$scope.$watch
和$scope.$apply
。官方文档没有帮助。
具体不懂的地方:
它们是否连接到 DOM? 如何更新模型的 DOM 更改? 它们之间的连接点是什么?我试过this tutorial,但对$watch
和$apply
的理解是理所当然的。
$apply
和 $watch
有什么作用,如何正确使用它们?
【问题讨论】:
【参考方案1】:您需要了解 AngularJS 的工作原理才能理解它。
摘要循环和$scope
首先,AngularJS 定义了一个所谓的消化循环的概念。这个循环可以被认为是一个循环,在这个循环中,AngularJS 检查所有$scope
s 观察的所有变量是否有任何变化。因此,如果您在控制器中定义了 $scope.myVar
并且此变量标记为被监视,那么您就隐含地告诉 AngularJS 在循环的每次迭代中监视 myVar
上的更改。 p>
一个自然的后续问题是:所有与$scope
相关的东西都被监视了吗?幸运的是,没有。如果您要注意 $scope
中每个对象的更改,那么很快,摘要循环将需要很长时间来评估,并且您很快就会遇到性能问题。这就是为什么 AngularJS 团队给了我们两种方法来声明一些 $scope
变量被监视(阅读下文)。
$watch 有助于监听 $scope 的变化
有两种方法可以将$scope
变量声明为被监视。
-
通过表达式
<span>myVar</span>
在模板中使用它
通过$watch
服务手动添加
广告 1)
这是最常见的场景,我相信你以前见过,但你不知道这在后台创建了一个手表。是的,它有!使用 AngularJS 指令(例如ng-repeat
)也可以创建隐式监视。
广告 2)
这就是您创建自己的手表的方式。当附加到$scope
的某些值发生更改时,$watch
服务可帮助您运行一些代码。它很少使用,但有时很有帮助。例如,如果您想在每次 'myVar' 更改时运行一些代码,您可以执行以下操作:
function MyController($scope)
$scope.myVar = 1;
$scope.$watch('myVar', function()
alert('hey, myVar has changed!');
);
$scope.buttonClicked = function()
$scope.myVar = 2; // This will trigger $watch expression to kick in
;
$apply 可以将更改与摘要循环集成
您可以将 $apply
函数视为一种集成机制。您会看到,每次您直接更改附加到$scope
对象的一些监视变量时,AngularJS 都会知道更改已经发生。这是因为 AngularJS 已经知道要监视这些更改。因此,如果它发生在框架管理的代码中,摘要循环将继续进行。
但是,有时您想更改 AngularJS 世界之外的某些值,然后查看更改正常传播。
考虑一下 - 你有一个 $scope.myVar
值,它将在 jQuery 的 $.ajax()
处理程序中进行修改。这将在未来的某个时候发生。 AngularJS 不能等待这种情况发生,因为它没有被指示等待 jQuery。
为了解决这个问题,引入了$apply
。它使您可以明确地启动消化周期。但是,你应该只使用它来将一些数据迁移到 AngularJS(与其他框架集成),而不要将此方法与常规 AngularJS 代码结合使用,因为 AngularJS 会抛出错误。
这一切与 DOM 有什么关系?
好吧,既然您已经了解了这一切,那么您真的应该再次按照教程进行操作。摘要循环将通过评估附加到所有$scope
s 的每个观察者来确保 UI 和 javascript 代码保持同步,只要没有任何变化。如果摘要循环中不再发生更改,则认为它已完成。
您可以在 Controller 中显式地将对象附加到 $scope
对象,或者直接在视图中以 expression
形式声明它们。
进一步阅读:
Make Your Own AngularJS, Part 1: Scopes And Digest【讨论】:
用这个怎么样? (“控制为”方法) 使用“Control as”应该对以上信息没有影响。使用 this.myVar 会将 myVar 置于范围内。 @ŁukaszBachman - “那么你明确告诉 Angular 监视更改”。我认为它是“隐式”而不是“显式”【参考方案2】:在 AngularJS 中,我们更新模型,视图/模板“自动”更新 DOM(通过内置或自定义指令)。
$apply 和 $watch 都是 Scope 方法,与 DOM 无关。
Concepts 页面(“运行时”部分)很好地解释了 $digest 循环、$apply、$evalAsync 队列和 $watch 列表。以下是文字随附的图片:
任何可以访问范围的代码——通常是控制器和指令(它们的链接函数和/或它们的控制器)——都可以设置一个“watchExpression”,AngularJS 将根据该范围进行评估。每当 AngularJS 进入它的 $digest 循环(特别是“$watch list”循环)时,就会发生这种评估。你可以观察单个作用域的属性,你可以定义一个函数来同时观察两个属性,你可以观察一个数组的长度等等。
当事情发生在“AngularJS 内部”时——例如,你在一个启用了 AngularJS 双向数据绑定的文本框中输入内容(即,使用 ng-model),触发一个 $http 回调等——已经调用了 $apply ,所以我们在上图中的“AngularJS”矩形内。将评估所有 watchExpressions(可能不止一次 - 直到没有检测到进一步的更改)。
当事情发生在“AngularJS 之外”——例如,你在指令中使用了 bind(),然后该事件触发,导致你的回调被调用,或者一些 jQuery 注册的回调被触发——我们仍然在“Native”长方形。如果回调代码修改了任何 $watch 正在观察的任何内容,则调用 $apply 进入 AngularJS 矩形,导致 $digest 循环运行,因此 AngularJS 会注意到更改并发挥其魔力。
【讨论】:
我理解这个想法,我不明白数据实际上是如何传输的。我有一个模型,它是一个包含大量数据的对象,我使用其中的一些来操作 DOM。然后其中一些被改变。如何将更改的数据放在模型中的正确位置?在我使用的示例中,他进行了操作,最后只使用了scope.$apply(scope.model)
,我不明白哪些数据被传输以及如何传输到模型中的正确位置?
没有发生神奇的数据传输。通常对于 Angular 应用程序,您应该更改 Angular 模型,然后驱动视图/DOM 更新。如果您在 Angular 之外更新 DOM,则必须手动更新模型。 scope.$apply(scope.model)
将简单地将 scope.model
评估为 Angular 表达式,然后进入 $digest 循环。在您引用的文章中,scope.$apply()
可能就足够了,因为该模型已经被 $watch 了。 stop() 函数正在更新模型(我相信 toUpdate 是对 scope.model 的引用),然后调用了 $apply。
看起来 AngularJS 文档已从此答案下移出(第一个链接在页面上没有“运行时”或 $watch
,第二个链接已损坏——截至目前,无论如何)。痛苦的是,the archive versions 没有缓存任何异步进程创建的内容。【参考方案3】:
AngularJS 扩展了这个 events-loop,创建了一个名为 AngularJS context
的东西。
$watch()
每次在 UI 中绑定某些内容时,您都会在 $watch
列表中插入 $watch
。
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
这里有$scope.user
,它绑定到第一个输入,我们有$scope.pass
,它绑定到第二个输入。为此,我们将两个 $watch
es 添加到 $watch
列表中。
当我们的 template 被加载时,也就是链接阶段,编译器将查找每个指令并创建所有需要的 $watch
es。
AngularJS 提供$watch
、$watchcollection
和$watch(true)
。下面是一个简洁的图表,解释了来自watchers in depth 的所有三个。
angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout)
$scope.users = ["name": "vinoth","name":"yusuf","name":"rajini"];
$scope.$watch("users", function()
console.log("**** reference checkers $watch ****")
);
$scope.$watchCollection("users", function()
console.log("**** Collection checkers $watchCollection ****")
);
$scope.$watch("users", function()
console.log("**** equality checkers with $watch(true) ****")
, true);
$timeout(function()
console.log("Triggers All ")
$scope.users = [];
$scope.$digest();
console.log("Triggers $watchCollection and $watch(true)")
$scope.users.push( name: 'Thalaivar');
$scope.$digest();
console.log("Triggers $watch(true)")
$scope.users[0].name = 'Superstar';
$scope.$digest();
);
http://jsfiddle.net/2Lyn0Lkb/
$digest
循环
当浏览器接收到可由 AngularJS 上下文管理的事件时,$digest
循环将被触发。这个循环由两个较小的循环组成。一个处理$evalAsync
队列,另一个处理$watch list
。 $digest
将遍历我们拥有的$watch
列表
app.controller('MainCtrl', function()
$scope.name = "vinoth";
$scope.changeFoo = function()
$scope.name = "Thalaivar";
);
name
<button ng-click="changeFoo()">Change the name</button>
这里我们只有一个$watch
,因为 ng-click 不会创建任何手表。
我们按下按钮。
-
浏览器接收到将进入 AngularJS 上下文的事件
$digest
循环将运行,并要求每个 $watch 进行更改。
因为$watch
正在监视 $scope.name 的变化
报告更改,它将强制执行另一个 $digest
循环。
新循环不报告任何内容。
浏览器取回控件并将更新 DOM
反映 $scope.name 的新值
这里重要的是每个进入 AngularJS 上下文的事件都会运行一个$digest
循环。这意味着每次我们在输入中写一个字母时,循环都会运行检查此页面中的每个$watch
。
$apply()
如果您在触发事件时调用$apply
,它将通过角度上下文,但如果您不调用它,它将在它之外运行。就是这么简单。 $apply
将在内部调用$digest()
循环,它会遍历所有手表以确保 DOM 使用新更新的值进行更新。
$apply()
方法将触发整个$scope
链上的观察者,而$digest()
方法将仅触发当前$scope
及其children
上的观察者。 当没有更高级别的$scope
对象需要知道本地更改时,您可以使用$digest()
。
【讨论】:
【参考方案4】:我发现了涵盖$watch
、$apply
、$digest
和摘要循环的非常深入的视频:
AngularJS - Understanding Watcher, $watch, $watchGroup, $watchCollection, ng-change
AngularJS - Understanding digest cycle (digest phase or digest process or digest loop)
AngularJS Tutorial - Understanding $apply and $digest (in depth)
以下是这些视频中用于解释概念的几张幻灯片(以防万一,如果上述链接被删除/不起作用)。
在上图中,“$scope.c”没有被监视,因为它没有用于任何数据绑定(在标记中)。其他两个($scope.a
和 $scope.b
)将被观看。
从上图看:AngularJS 根据各自的浏览器事件捕获事件,执行摘要循环(遍历所有监视以进行更改),执行监视函数并更新 DOM。如果不是浏览器事件,可以使用$apply
或$digest
手动触发摘要循环。
更多关于$apply
和$digest
:
【讨论】:
【参考方案5】:还有$watchGroup
和$watchCollection
。具体来说,$watchGroup
如果你想调用一个函数来更新一个在不是 dom 对象的视图中具有多个属性的对象,例如画布中的另一个视图、WebGL 或服务器请求。
这里,文档link。
【讨论】:
我会评论$watchCollection
,但我看到你已经这样做了。 Here is documentation 来自 AngularJS 站点。它们提供了$watch
深度的非常好的视觉效果。请注意,该信息靠近页面底部。【参考方案6】:
看完以上所有内容,无聊又困倦(对不起,但这是真的)。非常技术性,深入,详细和干燥。 我为什么要写作?因为 AngularJS 非常庞大,许多相互关联的概念会让任何人发疯。我经常问自己,我不够聪明,无法理解他们吗?不!这是因为很少有人能用傻瓜语言来解释这项技术,而无需使用所有术语! 好的,让我试试:
1) 它们都是事件驱动的。(我听到了笑声,但请继续阅读)
如果你不知道什么是事件驱动那么 认为您放置了一个按钮 在页面上,使用“点击”功能将其连接起来,等待 用户点击它来触发你在里面植入的动作 功能。或者想想 SQL Server / Oracle 的“触发器”。
2) $watch 是“点击”。
它的特别之处在于它需要2个函数作为参数,第一个 给出事件的值,第二个将值放入 考虑...
3) $digest 是不知疲倦检查的老大, bla-bla-bla 但是一个好老板。
4) $apply 为您提供手动操作的方法,例如防故障(如果点击无法启动,您可以强制它运行。)
现在,让我们让它可视化。想象一下,这样更容易 抓住这个想法:
在餐厅里,
- 服务员
应该接受客户的订单,这是
$watch(
function()return orders;,
function()Kitchen make it;
);
- 经理跑来跑去,确保所有服务员都醒着,对客户的任何变化迹象做出反应。这是$digest()
- OWNER 拥有根据要求驾驶所有人的终极权力,这是$apply()
【讨论】:
这个5岁就可以理解了。我很欣赏这种回答。 +1 "我经常问自己,难道我不够聪明,无法理解它们吗?不!" 抱歉。我笑了。以上是关于如何在 AngularJS 中使用 $scope.$watch 和 $scope.$apply?的主要内容,如果未能解决你的问题,请参考以下文章
MobileFirst:如何使用 angularJS 将参数传递给适配器