协慌网

登录 贡献 社区

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

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

具体我不明白:

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

我试过这个教程 ,但需要理解$watch$apply是理所当然的。

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

答案

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

摘要周期和 $ 范围

首先,AngularJS 定义了一个所谓的摘要周期的概念。这个循环可以被认为是一个循环,在此过程中 AngularJS 会检查所有$scope s 所监视的所有变量是否有任何变化。因此,如果您在控制器中定义了$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 有什么关系?

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

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

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

进一步阅读:

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

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

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

$ digest循环

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

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

当事情发生在 “AngularJS 之外” - 例如,你在一个指令中使用了 bind()然后该事件触发,导致你的回调被调用,或者一些 jQuery 注册的回调触发 - 我们仍然在 “Native” 矩形中。如果回调代码修改任何 $ 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() ,因此所有数据绑定都会更新。