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

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

我不明白的是:

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

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

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

答案

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

消化周期和作用域

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

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

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

有两种方法可以声明要监视的$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 有什么关系?

好了,既然您已经知道了所有这些,那么您应该再次真正遵循该教程。只要不进行任何更改,摘要周期就可以通过评估附加到所有$scope的每个观察程序来确保 UI 和 JavaScript 代码保持同步。如果摘要循环中没有更多更改发生,则视为已完成。

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

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

进一步阅读:

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

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

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

$ digest循环

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

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

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

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绑定到第二个输入。这样做,我们将两个$watch es添加$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/

$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 中的 $ watch 报告了更改,它将强制另一个$digest循环。
  4. 新循环不报告任何内容。
  5. 浏览器将控件取回,它将更新反映 $ scope.name 新值的 DOM。
  6. 这里重要的是,进入 AngularJS 上下文的每个事件都将运行$digest循环。这意味着,每当我们在输入中写一个字母时,循环便会检查该页面中的每个$watch

$ apply()

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

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

我发现非常深入的视频涵盖了$watch$apply$digest和 digest 循环,它们位于:

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

在此处输入图片说明

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

在此处输入图片说明

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

有关$apply$digest更多信息:

在此处输入图片说明

还有$watchGroup$watchCollection 。具体来说,如果要调用一个函数来更新在非 dom 对象的视图中具有多个属性的对象(例如,canvas,webGL 或服务器请求中的其他视图), $watchGroup确实很有帮助。在这里,文档链接

刚读完以上所有内容,无聊又困(很抱歉,是真的)。非常技术性,深入,详细和干燥。我为什么写作?由于 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()