如何在AngularJS中使用$ scope。$ watch和$ scope。$ apply?


1088

我不知道如何使用$scope.$watch$scope.$apply。官方文档没有帮助。

我不明白的是:

  • 他们连接到DOM吗?
  • 如何更新对模型的DOM更改?
  • 它们之间的连接点是什么?

我尝试了本教程,但是它理解了$watch并且$apply是理所当然的。

做什么$apply$watch做什么,以及如何正确使用它们?

Answers:


1737

您需要了解AngularJS的工作原理才能理解它。

消化周期和作用域

首先,AngularJS定义了所谓的摘要循环的概念。此循环可以视为一个循环,在此循环中AngularJS检查所有s 监视的所有变量是否有任何更改$scope。因此,如果您已$scope.myVar在控制器中定义并且该变量被标记为“正在监视”,那么您将隐式告诉AngularJS监视myVar循环的每次迭代中的更改。

一个自然而然的跟进问题是:是否一切都与$scope被监视有关?幸运的是,没有。如果您要监视中的每个对象的更改$scope,那么摘要循环很快就会花费很多时间进行评估,并且您会很快遇到性能问题。这就是AngularJS团队为我们提供了两种方法来声明某些$scope变量被监视的原因(请参阅下文)。

$ watch有助于监听$ scope的变化

有两种方法将$scope变量声明为被监视。

  1. 通过表达式在模板中使用它 <span>{{myVar}}</span>
  2. 通过$watch服务手动添加

广告1)这是最常见的情况,我敢肯定您以前看过它,但是您不知道这是在后台创建手表的。是的,它有!使用AngularJS指令(例如ng-repeat)也可以创建隐式监视。

广告2)这就是您制作手表的方式$watch当附加到的值$scope更改时,service服务可以帮助您运行一些代码。它很少使用,但有时会有所帮助。例如,如果您希望每次“ 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只要没有任何变化,摘要周期就可以通过评估附加到所有的每个观察程序来确保UI和JavaScript代码保持同步。如果摘要循环中没有更多更改发生,则视为已完成。

您可以$scope在Controller中显式地将对象附加到对象,也可以{{expression}}直接在视图中以形式声明它们。

我希望这有助于澄清有关这一切的一些基本知识。

进一步阅读:


57
“ Angular检查所有$ scopes附带的所有变量是否有任何更改” –我认为那是不对的。我相信只有Angular(脏)会检查已设置$ watchs的$ scope属性(请注意,在视图中使用{{}}会自动创建$ watch)。另请参阅“作用域” 页面上的““作用域$ watch性能注意事项””部分。
Mark Rajcok

5
可能是这样。我会尝试抽出一些时间来阅读更多有关该内容的信息并编辑我的答案。
ŁukaszBachman

15
@MarkRajcok,你是对的。我更改了答复,并指出了一篇很好地展示了如何实现此方法的文章。
ŁukaszBachman

3
那怎么用呢?(“控制方式”方法)
Leandro 2014年

2
使用“控件为”对以上信息没有影响。使用this.myVar将myVar放在示波器上。
MarcusRådell,2014年

161

在AngularJS中,我们更新模型,并且视图/模板“自动”(通过内置或自定义指令)更新DOM。

$ apply和$ watch都是Scope方法,与DOM不相关。

概念页(参考“运行”)具有消化$循环的一个很好的解释,$应用中,$ evalAsync队列和$观察名单。这是文本随附的图片:

$ digest循环

无论什么代码可以访问范围(通常是控制器和指令(它们的链接函数和/或其控制器)),都可以设置“ watchExpression ”,AngularJS将在该范围内对其进行评估。每当AngularJS进入其$ digest循环(尤其是“ $ watch list”循环)时,都会进行此评估。您可以观察单个作用域属性,可以定义一个函数来一起观察两个属性,可以观察数组的长度,等等。

当事情发生在“ AngularJS内部”时–例如,您键入一个启用了AngularJS双向数据绑定的文本框(即,使用ng-model),触发$ http回调等。–已经调用$ apply,因此我们在上图中的“ AngularJS”矩形内。将对所有watchExpressions进行评估(可能不止一次-直到未检测到进一步的更改为止)。

当事情发生在“ AngularJS外部”时(例如,您在指令中使用bind(),然后触发该事件,导致您的回调被调用,或者某些jQuery注册的回调被触发),我们仍然位于“本机”矩形中。如果回调代码修改了任何$ watch正在监视的内容,请调用$ apply以进入AngularJS矩形,从而导致$ digest循环运行,因此AngularJS将注意到这一变化并发挥其魔力。


5
我了解这个主意,但我不了解的是如何实际传输数据。我有一个模型,它是一个包含大量数据的对象,我使用其中的一些模型来操纵DOM。然后其中一些会被更改。如何将更改的数据放在模型中的正确位置?在我使用的示例中,他进行了操作,最后仅使用了scope.$apply(scope.model),我不知道要传输什么数据以及如何将其传输到模型中的正确位置?
ilyo

6
没有神奇的数据传输发生。通常,对于Angular应用程序,您应该更改Angular模型,然后驱动视图/ DOM更新。如果您在Angular之外更新DOM,则必须手动更新模型。 scope.$apply(scope.model)将简单地scope.model作为Angular表达式求值,然后进入$ digest循环。在您引用的文章中,可能scope.$apply()已经足够了,因为该模型已经被监视了。stop()函数正在更新模型(我相信toUpdate是对scope.model的引用),然后调用$ apply。
Mark Rajcok

看来AngularJS文档已从此答案下移了出来(第一个链接没有“运行时”或$watch页面上,而第二个链接已断开-到目前为止,无论如何)。痛苦的是,存档版本没有缓存任何异步过程创建的内容。
鲁芬

52

AngularJS扩展了这个事件循环,创建了一个叫做AngularJS context

$ watch()

每次在UI中绑定内容时,都会$watch$watch列表中插入一个。

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

在这里,我们有$scope.user,它绑定到第一个输入,而我们有$scope.pass,它绑定到第二个输入。这样做,我们将两个es添加$watch$watch列表中

加载我们的模板(即链接阶段)时,在链接阶段,编译器将查找每个指令并创建$watch所需的所有es。

AngularJS提供$watch$watchcollection$watch(true)。下面是一张简洁的图表,深入地解释了从观察者身上提取的所有三种情况。

在此处输入图片说明

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不会创建任何手表。

我们按下按钮。

  1. 浏览器收到一个事件,该事件将进入AngularJS上下文
  2. $digest循环将运行,并将要求每个$ watch进行更改。
  3. 由于$watch正在监视$ scope.name的更改报告了更改,因此它将强制执行另一个$digest循环。
  4. 新循环不报告任何内容。
  5. 浏览器将控件取回,它将更新反映$ scope.name新值的DOM。
  6. 这里重要的是,进入AngularJS上下文的每个事件都将运行一个$digest循环。这意味着,每当我们在输入中写一个字母时,循环将运行检查$watch此页面中的每个字母。

$ apply()

如果$apply在事件触发时调用,它将经过角度上下文,但是如果不调用,它将在事件外部运行。就是这么简单。$apply将呼叫$digest()内部循环,并将迭代所有监视,以确保使用新更新的值来更新DOM。

$apply()方法将触发整个$scope链上的监视程序,而该$digest()方法将仅触发当前$scope及其链表上的监视程序children当没有任何上层$scope对象需要了解本地更改时,可以使用$digest()


18

我发现了非常深入的视频覆盖$watch$apply$digest并在消化周期:

以下是这些视频中使用的几张幻灯片来解释这些概念(以防万一,如果上述链接被删除/不起作用)。

在此处输入图片说明

在上图中,由于未在任何数据绑定(在标记中)使用“ $ scope.c”,因此未对其进行监视。其他两个($scope.a$scope.b)将被监视。

在此处输入图片说明

从上图可以看出:AngularJS基于各自的浏览器事件,捕获事件,执行摘要循环(遍历所有监视更改),执行监视功能并更新DOM。如果不是浏览器事件,则可以使用$apply或手动触发摘要周期$digest

有关$apply和的更多信息$digest

在此处输入图片说明


17

也有$watchGroup$watchCollection。特别$watchGroup是,如果您要调用一个函数来更新不是dom对象的视图中具有多个属性的对象(例如,画布,WebGL或服务器请求中的另一个视图),则确实有帮助。

在这里,文档链接


我会对此发表评论,$watchCollection但我看到您已经这样做了。 这是 AngularJS网站上有关它的文档。它们提供了很好的$watch深度视觉效果。请注意,该信息靠近页面底部。
JabberwockyDecompiler 2015年

15

刚读完以上所有内容,无聊又困(很抱歉,是真的)。非常技术性,深入,详细和干燥。我为什么写作?由于AngularJS庞大,因此许多相互关联的概念会让任何人发疯。我经常问自己,我不够聪明,无法理解它们吗?没有!这是因为很少有人能够用傻瓜语言来解释技术所有术语!好吧,让我尝试:

1)它们都是事件驱动的东西。(我听到笑声,但继续读)

如果您不知道事件驱动的是什么,那就以为您在页面上放置了一个按钮,并使用“ on-click”将其与一个函数挂钩,等待用户单击它来触发您在页面内部植入的动作。功能。或想到SQL Server / Oracle的“触发器”。

2)$ watch是“点击”的。

特殊之处在于它使用2个函数作为参数,第一个函数从事件中给出值,第二个函数将值考虑在内...

3)$ digest是不知疲倦地四处检查的老板,但又是一个好老板。

4)$ apply为您提供了一种手动进行操作的方式,例如一种防故障功能(如果单击不起作用,则可以强制其运行。)

现在,让我们将其可视化。想象一下,使它更容易掌握:

在一家餐馆,

- 服务员

应该接受客户的订单,这是

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

-经理到处跑来确保所有服务员都醒着,以响应客户的任何变化迹象。这是$digest()

-OWNER有权根据要求驱动所有人$apply()


2
五岁的孩子可以理解这一点。我感谢这种回答。+1
克里斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.