繁体   English   中英

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

[英]How do I use $scope.$watch and $scope.$apply in AngularJS?

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

我不明白的具体内容:

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

我试过这个教程,但它理所当然地需要理解$watch$apply

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

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

摘要循环和 $scope

首先,AngularJS 定义了一个所谓的摘要循环的概念。 这个循环可以看作是一个循环,在此期间 AngularJS 检查所有$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附加的某些值发生更改时运行某些代码。 它很少使用,但有时很有帮助。 例如,如果您想在每次 '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 $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函数如何将范围作为参数(名称中没有$)。 通过此参数,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() ,因此所有数据绑定都会更新。

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

$手表()

每次在 UI 中绑定某些东西时,都会$watch list 中插入一个$watch

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

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

当我们的模板被加载时,也就是在链接阶段,编译器将查找每个指令并创建所有需要的$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 变化的 $watch 报告了一个变化,它将强制另一个$digest循环。
  4. 新循环什么都不报告。
  5. 浏览器重新获得控制权,它将更新 DOM 以反映 $scope.name 的新值
  6. 这里重要的是每个进入 AngularJS 上下文的事件都会运行一个$digest循环。 这意味着每次我们在输入中写入一个字母时,循环都会运行检查此页面中的每个$watch

$应用()

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

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

我发现了非常深入的视频,其中涵盖了$watch$apply$digest和 digest 循环:

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

在此处输入图片说明

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

在此处输入图片说明

上图:AngularJS 根据浏览器的事件,捕获事件,执行摘要循环(遍历所有的 watch 以进行更改),执行 watch 函数并更新 DOM。 如果不是浏览器事件,可以使用$apply$digest手动触发摘要循环。

更多关于$apply$digest

在此处输入图片说明

还有$watchGroup$watchCollection 具体来说,如果你想调用一个函数来更新一个对象,该对象在一个不是 dom 对象的视图中具有多个属性,例如画布、 WebGL或服务器请求中的另一个视图,那么$watchGroup真的很有帮助。

在这里,文档链接

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

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

如果你不知道什么是事件驱动然后认为你在页面上放置了一个按钮,使用“on-click”将它与一个函数连接起来,等待用户点击它来触发你在里面植入的动作功能。 或者想想 SQL Server/Oracle 的“触发器”。

2) $watch 是“点击”。

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

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

4) $apply 为您提供手动操作的方式,例如防故障(如果单击未启动,则强制它运行。)

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

在一家餐馆,

- 服务员

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM