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

我不太明白的是:

它们是否连接到DOM? 如何将DOM更改更新到模型? 它们之间的连接点是什么?

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

$apply和$watch做什么,如何适当地使用它们?


为了理解AngularJS,你需要了解它是如何工作的。

摘要周期和$范围

首先,AngularJS定义了一个所谓的摘要周期的概念。这个循环可以被认为是一个循环,在此期间AngularJS检查所有$作用域所监视的所有变量是否有任何变化。如果你有$scope。myVar在你的控制器中定义,并且这个变量被标记为被监视,然后你隐式地告诉AngularJS在每次循环迭代中监视myVar的变化。

一个自然的后续问题是:是否所有附加到$scope的内容都被监视?幸运的是,没有。如果您要观察$作用域中每个对象的更改,那么快速地计算摘要循环将花费很长时间,并且很快就会遇到性能问题。这就是为什么AngularJS团队给了我们两种声明$scope变量被监视的方法(阅读下文)。

$watch帮助监听$scope的变化

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

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

1)广告 这是最常见的场景,我相信你以前见过,但你不知道这在背景中创建了一个手表。是的,它有!使用AngularJS指令(比如ng-repeat)也可以创建隐式监视。

广告2) 这就是如何创建自己的手表。当附加到$作用域的某些值发生变化时,$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世界之外更改一些值,并看到更改正常传播。 考虑一下——您有一个$作用域。myVar值,将在jQuery的$.ajax()处理程序中修改。这将在未来的某个时候发生。AngularJS不能等待这个发生,因为它还没有被指示等待jQuery。

为了解决这个问题,引入了$apply。它让你明确地开始消化周期。但是,你应该只使用这个方法将一些数据迁移到AngularJS(与其他框架集成),而不要将这个方法与常规的AngularJS代码结合使用,因为AngularJS会抛出一个错误。

所有这些与DOM有什么关系?

好吧,既然你知道了所有这些,你真的应该再跟着教程走一遍。摘要周期将确保UI和JavaScript代码保持同步,只要没有任何变化,就会评估附加到所有$作用域的每个观察者。如果在摘要循环中没有发生更多的更改,则认为它已完成。

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

进一步阅读:

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


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

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

概念页面(“运行时”部分)对$digest循环、$apply、$evalAsync队列和$watch列表有很好的解释。下面是文字配图:

任何可以访问作用域的代码——通常是控制器和指令(它们的链接函数和/或它们的控制器)——都可以设置一个“watchExpression”,AngularJS将根据该作用域对其求值。当AngularJS进入$digest循环(特别是“$watch list”循环)时,这个计算就会发生。你可以观察单个作用域属性,你可以定义一个函数来观察两个属性,你可以观察一个数组的长度,等等。

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

当事情发生在“AngularJS外部”时——例如,你在指令中使用bind(),然后该事件触发,导致你的回调被调用,或者一些jQuery注册的回调被触发——我们仍然在“Native”矩形中。如果回调代码修改了任何$watch正在监视的东西,调用$apply进入AngularJS矩形,导致$digest循环运行,因此AngularJS会注意到这个变化并执行它的魔法。


还有$watchGroup和$watchCollection。具体来说,$watchGroup是非常有用的,如果你想调用一个函数来更新一个对象,这个对象在一个视图中有多个属性,而不是dom对象,例如画布,WebGL或服务器请求中的另一个视图。

这里是文档链接。


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

看()美元

每次你在UI中绑定一些东西时,你都会在$watch列表中插入一个$watch。

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

这里我们有$scope。User,它绑定到第一个输入,我们有$scope。通过,这是绑定到第二个。这样我们就向$watch列表中添加了两个$watch。

当我们的模板被加载时,也就是在链接阶段,编译器会寻找每一个指令并创建所有需要的$watch。

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/

美元消化循环

当浏览器接收到一个可以由AngularJS上下文管理的事件时,$digest循环将被触发。这个循环是由两个较小的循环组成的。一个处理$evalAsync队列,另一个处理$watch列表。$摘要将循环遍历我们拥有的$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没有创建任何手表。

我们按下按钮。

The browser receives an event which will enter the AngularJS context The $digest loop will run and will ask every $watch for changes. Since the $watch which was watching for changes in $scope.name reports a change, it will force another $digest loop. The new loop reports nothing. The browser gets the control back and it will update the DOM reflecting the new value of $scope.name The important thing here is that EVERY event that enters the AngularJS context will run a $digest loop. That means that every time we write a letter in an input, the loop will run checking every $watch in this page.

$应用()

如果你在事件触发时调用$apply,它将穿过angular上下文,但如果你不调用它,它将在它外部运行。就这么简单。$apply将在内部调用$digest()循环,它将遍历所有的手表,以确保DOM使用新更新的值进行更新。

$apply()方法会触发整个$作用域链上的观察者,而$digest()方法只会触发当前$作用域及其子域上的观察者。当没有更高级别的$scope对象需要知道局部更改时,您可以使用$digest()。


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

AngularJS -理解Watcher, $watch, $watchGroup, $watchCollection, ng-change AngularJS -理解摘要周期(摘要阶段或摘要过程或摘要循环) AngularJS教程-深入理解$apply和$digest

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

在上面的图像中,“$scope.c”没有被监视,因为它没有用于任何数据绑定(在标记中)。另外两个($scope。A和$scope.b)将被监视。

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

关于$apply和$digest的更多信息:


读完上面所有的,无聊又困(抱歉,但这是真的)。非常专业,深入,详细,枯燥。 我为什么要写作?因为AngularJS是庞大的,很多相互关联的概念可以让任何人发疯。我经常问自己,难道我不够聪明,不能理解它们吗?不!这是因为很少有人能用一种不含所有术语的傻瓜语言来解释这项技术! 好吧,让我试试:

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

如果你不知道什么是事件驱动那么 假设你放了一个按钮 在页面上,用“On -click”函数将其连接起来,等待 用户点击它来触发你在植物里面的动作 函数。或者想想SQL Server / Oracle的“触发器”。

2) $watch是“on-click”。

它的特殊之处在于它以两个函数作为参数,第一个 给出事件的值,第二个函数取事件的值 考虑……

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

4)当你想手动执行时,$apply为你提供了一种方法,就像一个防故障(以防on-click不起作用,你可以强制它运行)。

现在,让我们让它可视化。想象一下,这样更容易 抓住这个概念:

在餐厅里,

——服务员

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

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

-经理四处走动,确保所有服务员都是清醒的,对顾客的任何变化做出反应。这是$digest()

- OWNER拥有最终的权力来驱动每个人的请求,这是$apply()