题 如何使用$ scope。$ watch和$ scope。$在AngularJS中申请?


我不明白怎么用 $scope.$watch 和 $scope.$apply。官方文档没有帮助。

具体我不明白:

  • 它们是否与DOM连接?
  • 如何更新模型的DOM更改?
  • 他们之间的联系点是什么?

我试过了 本教程,但需要了解 $watch 和 $apply 理所当然。

做什么 $apply 和 $watch 做,我该如何正确使用它们?


1009
2018-02-27 12:50


起源




答案:


您需要了解AngularJS如何工作才能理解它。

摘要周期和$范围

首先,AngularJS定义了一个所谓的概念 消化周期。可以将此循环视为循环,在此期间AngularJS会检查所有变量是否有任何更改 看着 所有的 $scope秒。所以,如果你有 $scope.myVar 在你的控制器中定义,这个变量是 标记为被观看,然后你隐含地告诉AngularJS监控变化 myVar 在循环的每次迭代中。

一个自然的后续问题是:一切都依附于 $scope 被监视?幸运的是,没有。如果您要观察对您的每个对象的更改 $scope然后很快消化循环需要很长时间来评估,你很快就会遇到性能问题。这就是为什么AngularJS团队给了我们两种方式来宣告一些 $scope 被观察的变量(见下文)。

$ watch有助于监听$ scope更改

有两种方式来声明a $scope 变量如被观看。

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

广告1) 这是最常见的情况,我相信你以前见过它,但你不知道这在后台创造了一个手表。是的,它有!使用AngularJS指令(例如 ng-repeat)还可以创建隐式手表。

广告2) 这就是你创建自己的方式 手表$watch 服务可以帮助您在附加一些值时运行一些代码 $scope 已经改变。它很少使用,但有时很有帮助。例如,如果您希望每次'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有什么关系?

好吧,你应该再次按照教程,既然你知道这一切。摘要周期将通过评估附加到所有观察者的每个观察者来确保UI和JavaScript代码保持同步 $scope只要没有变化。如果摘要循环中没有更多的更改,则认为它已完成。

您可以将对象附加到 $scope 对象在Controller中显式显示,或者通过声明它们 {{expression}} 直接在视图中形成。

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

进一步阅读:


1652
2018-02-27 13:14



“角度检查附加到所有$ scopes的所有变量是否有任何变化” - 我不认为这是正确的。我相信只有Angular(脏)检查已设置$ watch的$ scope属性(请注意,在视图中使用{{}}将自动创建$ watch)。另请参阅“范围$ watch性能注意事项”部分 范围页面。 - Mark Rajcok
情况可能就是这样。我会试着找一些时间来阅读更多关于它并编辑我的答案。 - ŁukaszBachman
@MarkRajcok,你是对的。我改变了我的回复并指出了一篇很好地展示了如何实现的文章。 - ŁukaszBachman
用这个怎么样? (“控制为”方法) - Leandro Tupone
使用“控制为”应该对上述信息没有影响。使用this.myVar将myVar放在范围上。 - Marcus Nielsen


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

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

概念 页面(“运行时”一节)对$ digest循环,$ apply,$ evalAsync队列和$ watch列表有一个很好的解释。这是文本附带的图片:

$digest loop

无论什么代码都可以访问范围 - 通常是控制器和指令(它们的链接功能和/或它们的控制器) - 可以设置“watchExpression“AngularJS将针对该范围进行评估。只要AngularJS进入其$ digest循环(特别是”$ watch list“循环),就会发生此评估。您可以观看单个范围属性,您可以定义一个函数来同时监视两个属性,你可以看一下阵列的长度等。

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

当事情发生在“AngularJS之外” - 例如,你在一个指令中使用了bind()然后该事件触发,导致你的回调被调用,或者一些jQuery注册的回调触发 - 我们仍然在“Native”矩形中。如果回调代码修改任何$ watch正在观看的内容,请调用$ apply进入AngularJS矩形,导致$ digest循环运行,因此AngularJS会注意到更改并发挥其魔力。


152
2018-02-28 00:48



我理解这个想法,我不明白的是数据是如何实际传输的。我有一个模型,它是一个包含大量数据的对象,我使用它来操纵DOM。然后其中一些变了。如何将更改的数据放在模型中的正确位置?在我使用的例子中,他进行操作,最后简单地使用 scope.$apply(scope.model),我不明白哪些数据被传输以及如何将其传输到模型中的正确位置? - ilyo
没有发生神奇的数据传输。通常使用Angular应用程序,您应该更改Angular模型,然后驱动视图/ DOM更新。如果您在Angular之外更新DOM,则必须手动更新模型。 scope.$apply(scope.model) 只会评估 scope.model 作为Angular表达式,然后输入$ digest循环。在您可能引用的文章中 scope.$apply() 这已经足够了,因为该模型已经被观察了。 stop()函数正在更新模型(我相信toUpdate是对scope.model的引用),然后调用$ apply。 - Mark Rajcok


这个博客 已经涵盖所有创建示例和可理解的解释。

AngularJS $scope 功能 $watch(), $digest() 和 $apply() 是AngularJS的一些核心功能。理解 $watch()$digest() 和 $apply() 对于了解AngularJS至关重要。

当您从视图中的某个位置创建数据绑定到$ scope对象上的变量时,AngularJS会在内部创建一个“监视”。手表意味着AngularJS会监视变量的变化 $scope object。框架正在“观察”变量。手表是使用 $scope.$watch() 我将在本文后面介绍的功能。

在您的应用程序的关键点AngularJS调用 $scope.$digest() 功能。此函数遍历所有监视并检查是否有任何监视变量已更改。如果监视变量已更改,则调用相应的侦听器函数。监听器函数执行它需要做的任何工作,例如更改HTML文本以反映监视变量的新值。就这样 $digest() function是触发数据绑定更新的内容。

大多数时候AngularJS会调用$ scope。$ watch()和 $scope.$digest() 适合您的功能,但在某些情况下您可能需要自己调用它们。因此,了解它们的工作方式真的很棒。

$scope.$apply() function用于执行一些代码,然后调用 $scope.$digest() 之后,检查所有手表并调用相应的手表监听器功能。该 $apply() 将AngularJS与其他代码集成时,函数很有用。

我会详细介绍一下 $watch(), $digest() 和 $apply() 本文其余部分的函数。

$表()

$scope.watch() 函数创建一些变量的监视。注册手表时,您将两个功能作为参数传递给 $watch() 功能:

  • 价值功能
  • 听众功能

这是一个例子:

$scope.$watch(function() {},
              function() {}
             );

第一个函数是值函数,第二个函数是监听器函数。

值函数应返回正在监视的值。然后,AngularJS可以根据watch函数上次返回的值检查返回的值。这样AngularJS可以确定值是否已更改。这是一个例子:

$scope.$watch(function(scope) { return scope.data.myVar },
              function() {}
             );

这个示例valule函数返回 $scope 变量 scope.data.myVar。如果此变量的值发生更改,则将返回不同的值,AngularJS将调用侦听器函数。

注意value函数如何将范围作为参数(名称中没有$)。通过此参数,值函数可以访问 $scope 及其变量。如果你需要,值函数也可以观察全局变量,但大多数时候你会看一个 $scope 变量。

如果值已更改,则侦听器函数应执行其需要执行的操作。也许您需要更改另一个变量的内容,或者设置HTML元素或其他内容。这是一个例子:

$scope.$watch(function(scope) { return scope.data.myVar },
              function(newValue, oldValue) {
                  document.getElementById("").innerHTML =
                      "" + newValue + "";
              }
             );

此示例将HTML元素的内部HTML设置为变量的新值,嵌入在b元素中,使值变为粗体。当然,您可以使用代码完成此操作 {{ data.myVar },但这只是您在侦听器函数中可以执行的操作的示例。

$摘要()

$scope.$digest() 函数遍历所有的手表 $scope object,及其子$ scope对象(如果有的话)。什么时候 $digest() 迭代手表,它调用每个手表的价值功能。如果value函数返回的值与上次调用时返回的值不同,则调用该监视的监听器函数。

$digest() 只要AngularJS认为有必要,就会调用函数。例如,在执行按钮单击处理程序之后,或在执行之后 AJAX call returns(在执行了done()/ fail()回调函数之后)。

你可能会遇到AngularJS没有调用的一些极端情况 $digest() 适合你。您通常会通过注意数据绑定不更新显示的值来检测到这一点。在那种情况下,打电话 $scope.$digest() 它应该工作。或者,您也许可以使用 $scope.$apply() 相反,我将在下一节解释。

$适用()

$scope.$apply() 函数将函数作为执行的参数,然后执行 $scope.$digest() 被称为内部。这使您更容易确保检查所有手表,从而刷新所有数据绑定。这是一个 $apply() 例:

$scope.$apply(function() {
    $scope.data.myVar = "Another value";
});

函数传递给了 $apply() 函数作为参数将改变值 $scope.data.myVar。当函数退出AngularJS时会调用 $scope.$digest() 功能,以检查所有手表的观察值的变化。

说明如何 $watch()$digest()和 $apply() 工作,看看这个例子:

<div ng-controller="myController">
    {{data.time}}

    <br/>
    <button ng-click="updateTime()">update time - ng-click</button>
    <button id="updateTimeButton"  >update time</button>
</div>


<script>
    var module       = angular.module("myapp", []);
    var myController1 = module.controller("myController", function($scope) {

        $scope.data = { time : new Date() };

        $scope.updateTime = function() {
            $scope.data.time = new Date();
        }

        document.getElementById("updateTimeButton")
                .addEventListener('click', function() {
            console.log("update time clicked");
            $scope.data.time = new Date();
        });
    });
</script>

他的例子绑定了 $scope.data.time 变量到插值指令,它将变量值合并到HTML页面中。这个绑定在内部创建了一个手表 $scope.data.time variable

该示例还包含两个按钮。第一个按钮有一个 ng-click 听众附属于它。单击该按钮时 $scope.updateTime() 调用函数,然后调用AngularJS $scope.$digest() 以便更新数据绑定。

第二个按钮从控制器函数内部获取一个标准的JavaScript事件监听器。单击第二个按钮时,将执行侦听器功能。如您所见,两个按钮的侦听器函数几乎相同,但是当调用第二个按钮的侦听器函数时,不会更新数据绑定。那是因为 $scope.$digest() 执行第二个按钮的事件侦听器后不会调用。因此,如果单击第二个按钮,则时间会更新 $scope.data.time 变量,但永远不会显示新时间。

要修复它,我们可以添加一个 $scope.$digest() 调用按钮事件监听器的最后一行,如下所示:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    console.log("update time clicked");
    $scope.data.time = new Date();
    $scope.$digest();
});

而不是打电话 $digest() 在按钮监听器功能内你也可以使用 $apply() 功能如下:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    $scope.$apply(function() {
        console.log("update time clicked");
        $scope.data.time = new Date();
    });
});

注意如何 $scope.$apply() 函数是从按钮事件监听器内部调用的,以及如何更新的 $scope.data.time变量在作为参数传递给的函数内部执行 $apply() 功能。当。。。的时候 $apply() 函数调用完成AngularJS调用 $digest() 在内部,所以所有数据绑定都会更新。


54
2018-02-22 10:13





AngularJS对此进行了扩展 事件循环,创造一种叫做的东西 AngularJS context

$表()

每次在UI中绑定某些内容时,都会插入一个 $watch 在一个 $watch 名单

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

我们在这里 $scope.user,这是第一个输入,我们有 $scope.pass,这是第二个。这样做我们添加两个 $watches to the $watch 名单

当我们的 模板 加载,AKA在链接阶段,编译器将查找每个指令并创建所有 $watch需要的东西。

AngularJS提供 $watch$watchcollection 和 $watch(true)。下面是一张简洁的图表,解释了所有三个 观察者的深度

Enter image description here

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. 浏览器获得控制权,它将更新DOM 反映$ scope.name的新值
  6. 这里重要的是进入AngularJS上下文的每个事件都会运行一个 $digest 循环。这意味着每次我们在输入中写一个字母时,循环都会运行检查 $watch 在这个页面。

$适用()

如果你打电话 $apply 当一个事件被触发时,它将通过角度上下文,但是如果你不调用它,它将在它之外运行。就是这么简单。 $apply 会打电话给 $digest() 在内部循环,它将遍历所有监视以确保使用新更新的值更新DOM。

$apply() 方法将触发整个观察者 $scope 链而不是 $digest() 方法只会触发当前的观察者 $scope 和它的 children什么时候没有更高的 $scope 对象需要了解本地更改,您可以使用 $digest()


37
2018-04-22 13:06





$watchGroup 和 $watchCollection 同样。特别, $watchGroup 如果你想调用一个函数来更新一个在一个不是dom对象的视图中具有多个属性的对象,那将非常有用。 canvas,webGL或服务器请求中的其他视图。在这里,文档 链接


17
2018-03-18 10:50



我会对此发表评论 $watchCollection 但是我看到你已经做到了。 这是文档 关于它来自AngularJS网站。它们提供了非常好的视觉效果 $watch 深度。请注意,信息靠近页面底部。 - JabberwockyDecompiler


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

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

Enter image description here

在上图中,“$ scope.c”未被监视,因为它未在任何数据绑定中使用(在标记中)。另外两个($scope.a 和 $scope.b)将被观看。

Enter image description here

从上图:根据各自的浏览器事件,AngularJS捕获事件,执行摘要周期(通过所有手表进行更改),执行监视功能并更新DOM。如果不是浏览器事件,则可以使用手动触发摘要周期 $apply 要么 $digest

更多关于 $apply 和 $digest

Enter image description here


15
2017-11-20 16:28





刚读完以上所有内容,无聊而困倦(抱歉,但这是真的)。非常技术性,深入,细致和干燥。 我为什么要写作?因为AngularJS是庞大的,许多相互关联的概念可以让任何人变得疯狂。我经常问自己,我是不是很聪明才能理解它们?没有!这是因为很少有人可以解释这个技术 for dummie语言 没有所有术语! 好的,让我试试:

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

如果你不知道什么是事件驱动的那么   想你放一个按钮   在页面上,使用“点击”等功能将其连接起来,等待   用户单击它以触发您在其中植入的操作   功能。或者想一想SQL Server / Oracle的“触发器”。

2)$ watch是“点击”。 

有什么特别的,它需要2个函数作为参数,第一个   给出事件的值,第二个将值带入   考虑...

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

4)$ apply为您提供了手动操作的方式,就像防故障一样(如果点击后没有启动,则强制它运行。)

现在,让它让它成为视觉。想象一下,这样可以更容易地抓住这个想法:

在一家餐馆, 

- 服务员 应该接受客户的订单,这是

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

- 经理 四处奔走以确保所有服务员都醒着,以应对客户的任何变化。这是 $digest()

- 所有者 这是最好的力量,可以根据要求驱动每个人 $apply()


10
2018-02-07 01:20