提问



我不明白如何使用$scope.$watch$scope.$apply。官方文档没有用。


我不明白的具体内容:



  • 他们是否与DOM连接?

  • 如何更新模型的DOM更改?

  • 他们之间的联系点是什么?



我尝试了本教程,但理解$watch$apply是理所当然的。[141]


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

最佳参考


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


摘要周期和$ scope



首先,AngularJS定义了一个所谓的摘要周期的概念。这个循环可以被认为是一个循环,在此期间AngularJS检查所有$scope的所有变量观察是否有任何变化。因此,如果您在控制器中定义了$scope.myVar并且此变量被标记为正在观看,那么您隐含地告诉AngularJS在每次迭代中监视myVar的变化。循环。


一个自然的后续问题是:$scope的所有内容都被观看了吗?幸运的是,没有。如果您要观察$scope中每个对象的更改,那么很快就会需要花费很长时间来评估摘要循环,您很快就会遇到性能问题。这就是为什么AngularJS团队给我们两种方式来声明一些$scope变量被观察(见下文)。


$ watch有助于监听$ scope更改



有两种方法可以将$scope变量声明为被观察。



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


广告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的每个观察者,只要没有任何变化。如果摘要循环中没有更多的更改,则认为它已完成。


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


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


进一步阅读:



  • 制作自己的AngularJS,第1部分:范围和摘要


其它参考1


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


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


Concepts页面(Runtime部分)对$ digest循环,$ apply,$ evalAsync队列和$ watch列表有很好的解释。这是文本附带的图片:[143]





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


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


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

其它参考2


本博客已经涵盖了所有创建示例和可理解的解释。[145]


AngularJS $scope函数$watch(), $digest()$apply()是AngularJS中的一些核心函数。理解$watch()$digest()$apply()对于理解AngularJS至关重要。


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


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


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


$scope.$apply()函数用于执行某些代码,然后调用$scope.$digest(),因此检查所有监视并调用相应的监听侦听器函数。将AngularJS与其他代码集成时,$apply()函数非常有用。


我将在本文的其余部分详细介绍$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函数返回的值与上次调用时返回的值不同,则调用该监视的监听器函数。


只要AngularJS认为有必要,就会调用$digest()函数。例如,在执行了按钮单击处理程序之后,或者在AJAX调用返回之后(在执行了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()函数,以及如何在作为参数传递给$apply()函数的函数内执行$scope.data.time变量的更新。当$apply()函数调用在内部完成AngularJS调用$digest()时,所有数据绑定都会更新。

其它参考3


AngularJS扩展了这个 events-loop ,创建了一个名为AngularJS context的东西。


$手表()


每次在UI中绑定某些内容时,都会在$watch列表中插入 $watch


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


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


当我们的模板被加载,AKA处于链接阶段时,编译器将查找每个指令并创建所需的所有$watch es。


AngularJS提供$watch$watchcollection$watch(true)。下面是一个简洁的图表,解释了从观察者那里获得的所有三个。[146]


[147]


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/[148]


$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. 的新值
  7. 这里重要的是进入AngularJS上下文的每个事件都会运行$digest循环。这意味着每次我们在输入中写一个字母时,循环将运行检查此页面中的每个$watch



$申请()



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


$apply()方法将触发整个$scope链上的观察者,而$digest()方法仅触发当前$scope及其children上的观察者。 当高级$scope对象都不需要知道本地更改时,您可以使用$digest()

其它参考4


还有$watchGroup$watchCollection。具体来说,如果你想调用一个函数来更新一个在一个不是dom对象的视图中有多个属性的对象,那么$watchGroup真的很有帮助。 canvas,webGL或服务器请求中的其他视图。这里是文档链接。[149]

其它参考5


我发现了非常深入的视频,涵盖$watch$apply$digest和摘要周期:



  • AngularJS - 了解Watcher,$ watch,$ watchGroup,$ watchCollection,ng-change [150]

  • AngularJS - 了解摘要周期(摘要阶段或摘要过程或摘要循环) [151]

  • AngularJS教程 - 了解$ apply和$ digest(深度) [152]



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


[153]


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


[154]


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


更多关于$apply$digest:


[155]

其它参考6


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


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



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



2)$ watch是点击。



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



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


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


现在,让我们把它变成视觉效果。想象一下,让它更容易理解:


在餐厅


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


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


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


- OWNER 具有驱动每个人的最终能力,这是$apply()