首页 >web前端 >js教程 >掌握$ Watch在Angularjs

掌握$ Watch在Angularjs

Lisa Kudrow
Lisa Kudrow原创
2025-02-18 10:47:09780浏览

Mastering $watch in AngularJS

核心要点

  • AngularJS中的$watch函数是观察变量值或表达式变化的强大工具。检测到变化时,它会触发一个回调函数,该函数会在每次被监视的变量发生变化时执行。
  • $watch使用JavaScript的相等运算符(===)进行比较。如果新值与旧值不同,则触发回调函数。但是,需要注意的是,默认情况下,$watch只检查引用相等性,这意味着只有当为被监视的变量分配新值时,才会触发回调函数。
  • AngularJS还提供$watchGroup$watchCollection作为方便的快捷方式,分别用于设置具有相同回调函数的多个监视器或监视数组或对象。但是,这些方法只执行浅监视,只对引用更改做出反应。
  • 使用$watch,尤其是在多个变量上使用,可能会影响性能,因为需要在每个摘要周期中检查所有被监视变量的变化。开发人员应根据情况考虑使用$watchGroup$watchCollection,或限制被监视变量的数量以提高性能。

本文由Mark Brown同行评审。感谢所有SitePoint的同行评审员,使SitePoint的内容达到最佳状态!

AngularJS提供了许多不同的选项,可以通过三种不同的“watch”方法使用发布-订阅模式。每种方法都采用可选参数来修改其行为。

关于$watch的官方文档远非详尽:毕竟,这是一个困扰AngularJS v1整体的问题。即使是解释如何进行的在线资源,充其量也是零散的。

因此,最终,开发人员很难为特定情况选择正确的方法。对于AngularJS初学者来说尤其如此!结果可能会令人惊讶或不可预测,这不可避免地会导致错误。

在本文中,我将假设您熟悉AngularJS概念。如果您觉得需要复习,您可能需要阅读有关$scope、绑定以及$apply$digest的内容。

检查您的理解

例如,监视数组的第一个元素的最佳方法是什么?假设我们在我们的作用域上声明了一个数组,$scope.letters = ['A','B','C']

  • 当我们向数组添加元素时,$scope.$watch('letters', function () {...});是否会触发其回调函数?
  • 当我们更改其第一个元素时,它会触发吗?
  • $scope.$watch('letters[0]', function () {...});呢?它会以相同的方式工作,还是更好?
  • 在上面,数组元素是原始值:如果我们用相同的值替换第一个元素会怎样?
  • 现在假设数组包含对象:会发生什么?
  • $watch$watchCollection$watchGroup之间有什么区别?

如果您对所有这些问题感到困惑,请继续阅读。我的目标是通过几个示例尽可能清楚地说明这一点,从而引导您完成整个过程。

$scope.$watch

让我们从$scope.$watch开始。这是所有watch功能的核心:我们将看到的每种其他方法都只是$watch的便捷快捷方式。

使用$watch

现在,Angular的优点是您可以显式地使用相同的机制在控制器中执行由数据更改触发的复杂操作。例如,您可以对某些数据设置监视器,这些数据可以响应以下内容而更改:

  1. 超时
  2. UI
  3. 由Web Worker执行的复杂异步计算
  4. Ajax调用

您可以只设置一个侦听器来处理任何数据更改,无论是什么原因造成的。

但是,要这样做,您需要自己调用$scope.$watch

实践操作

让我们看看$rootscope.watch()的代码。

这是它的签名:function(watchExp, listener, objectEquality, prettyPrintExpression)

详细来说,它的四个参数:

  1. watchExp 被监视的表达式。它可以是函数或字符串,它在每个摘要周期中都会被评估。

    这里需要注意的一个关键方面是,如果表达式被评估为函数,则该函数需要是幂等的。换句话说,对于相同的输入集,它应该始终返回相同的输出。如果不是这种情况,Angular将假设被监视的数据已更改。反过来,这意味着它将继续检测差异并在摘要周期的每次迭代中调用侦听器。

  2. listener 一个回调函数,在首次设置监视器时触发,然后在摘要周期中每次检测到watchExp值的更改时触发。设置时的初始调用旨在为表达式存储初始值。

  3. objectEquality 当且仅当此值为true时,监视器将执行深度比较。否则,它执行浅比较,即仅比较引用。

    让我们以数组为例:$scope.fruit = ["banana", "apple"]

    objectEquality == false意味着只有重新分配fruit字段才会导致调用侦听器。

    我们还需要检查“深度”比较有多深:我们稍后会讨论这一点。

  4. prettyPrintExpression 如果传递,它将覆盖监视表达式。此参数并非旨在在对$watch()的正常调用中使用;它由表达式解析器内部使用。

    小心:正如您自己所看到的,当意外传递第四个参数时,很容易出现意外的结果。

现在我们准备回答引言中的一些问题。请查看本节的示例:

CodePen 示例

请随意熟悉它们;您可以直接比较行为差异,或按照文章中的顺序进行。

监视数组

因此,您需要监视作用域上的数组以进行更改,但是“更改”是什么意思?

假设您的控制器看起来像这样:

<code class="language-javascript">app.controller('watchDemoCtrl', ['$scope', function($scope){
    $scope.letters = ['A','B','C'];
}]);</code>

一种选择是使用这样的调用:

<code class="language-javascript">$scope.$watch('letters', function (newValue, oldValue, scope) {
    // 对 $scope.letters 执行任何操作
});</code>

在上面的回调中,newValueoldValue具有不言自明的含义,并且每次$digest周期调用它时都会更新。scope的含义也很直观,因为它保存对当前作用域的引用。

但是,关键是:何时会调用此侦听器?事实上,您可以添加、删除、替换letters数组中的元素,而不会发生任何事情。这是因为,默认情况下,$watch假定您只想要引用相等性,因此只有当您为$scope.letters分配新值时,才会触发回调函数。

如果您需要对数组的任何元素的更改采取行动,则需要将true作为第三个参数传递给watch(即作为上面描述的可选objectEquality参数的值)。

<code class="language-javascript">$scope.$watch('letters', function (newValue, oldValue, scope) {
    // 对 $scope.letters 执行任何操作
}, true);</code>

监视对象

对于对象,情况没有改变:如果objectEquality为false,您只需监视对该作用域变量的任何重新赋值,而如果为true,则每次更改对象中的元素时都会触发回调函数。

监视数组的第一个元素

值得注意的是,通过使用objectEquality === true监视数组,每次触发回调函数时,newValueoldValue将是整个数组的新旧值。因此,您必须将它们彼此进行比较才能了解实际发生了什么变化。

假设您只对数组中第一个元素(或第四个元素——原理相同)的更改感兴趣。由于Angular非常出色,它允许您这样做:您可以在作为第一个参数传递给$watch的表达式中以自然的方式表达它:

<code class="language-javascript">$scope.$watch('letters[4]', function (newValue, oldValue, scope) {
    //...
}, true);</code>

如果数组只有2个元素会怎样?没问题,除非您添加第四个元素,否则您的回调函数不会被触发。好吧,好的,从技术上讲,它会在您设置监视器时触发,然后只有在您添加第四个元素时才会触发。

如果您记录oldValue,您会看到在这两种情况下它都将是未定义的。将此与监视现有元素时发生的情况进行比较:在设置时,您仍然有oldValue == undefined。所以$watch无法处理!

现在一个更有趣的问题:我们在这里需要传递objectEquality === true吗?

简短的回答:对不起,没有简短的回答。

这确实取决于:

  • 在此示例中,由于我们正在处理原始值,因此我们不需要深度比较,因此我们可以省略objectEquality
  • 但是,假设我们有一个矩阵,例如$scope.board = [[1, 2, 3], [4, 5, 6]];,并且我们想监视第一行。然后我们可能希望在像$scope.board[0][1] = 7这样的赋值更改它时收到警报。

监视对象的字段

也许比监视数组中的任意元素更有用的是,我们可以监视对象中的任意字段。但这并不奇怪,对吧?毕竟,JavaScript中的数组对象。

<code class="language-javascript">app.controller('watchDemoCtrl', ['$scope', function($scope){
    $scope.letters = ['A','B','C'];
}]);</code>

深度比较有多深?

在这一点上,我们还需要阐明最后一个但至关重要的细节:如果我们需要监视一个复杂的嵌套对象,其中每个字段都是非原始值,会发生什么?例如树或图,或者只是一些JSON数据。

让我们检查一下!

首先,我们需要一个要监视的对象:

<code class="language-javascript">$scope.$watch('letters', function (newValue, oldValue, scope) {
    // 对 $scope.letters 执行任何操作
});</code>

让我们为整个对象设置监视器:我认为,到目前为止,很清楚的是,在这种情况下必须将objectEquality设置为true

<code class="language-javascript">$scope.$watch('letters', function (newValue, oldValue, scope) {
    // 对 $scope.letters 执行任何操作
}, true);</code>

问题是:如果像$scope.b.bb[1].bb2a = 7;这样的赋值发生,Angular是否会足够好心地让我们知道?

答案是:是的,幸运的是,它会(在之前的CodePen演示中查看)。

其他方法

$scope.$watchGroup

$watchGroup()真的是一种不同的方法吗?答案是否定的,它不是。

$watchGroup()是一个方便的快捷方式,允许您使用相同的回调函数设置多个监视器,并传递一个watchExpressions数组。

每个传递的表达式都将使用标准$scope.$watch()方法进行监视。

<code class="language-javascript">$scope.$watch('letters[4]', function (newValue, oldValue, scope) {
    //...
}, true);</code>

值得注意的是,使用$watchGroupnewValuesoldValues将保存表达式的值列表,包括发生更改的值和保持相同值的那些值,顺序与它们在第一个参数的数组中传递的顺序相同。

如果您检查了此方法的文档,您可能会注意到它没有采用objectEquality选项。这是因为它浅监视表达式,并且只对引用更改做出反应。

如果您使用下面的$watchGroup()演示进行操作,您可能会对一些细微之处感到惊讶。例如,unshift将导致调用侦听器,至少在某种程度上是这样:这是因为当将表达式列表传递给$watchGroup时,任何一个表达式触发都会导致执行回调函数。

CodePen 示例

此外,请注意,$scope.obj.b的任何子字段的任何更改都不会产生任何更新——只有为b字段本身分配新值才会产生更新。

$scope.$watchCollection

这是监视数组或对象的另一个便捷快捷方式。对于数组,当替换、删除或添加任何元素时,将调用侦听器。对于对象,当更改任何属性时。同样,$watchCollection()不允许objectEquality,因此它只会浅监视元素/字段,并且不会对它们的子字段的更改做出反应。

CodePen 示例

结论

希望这些示例能帮助您发现此Angular功能的强大功能,并了解使用正确的选项有多么重要。

随意复制CodePen并尝试在不同的上下文中使用这些方法,并且不要忘记在评论区留下您的反馈!

如果您想更深入地了解我们在本文中讨论的一些概念,以下是一些进一步阅读的建议:

  1. AngularJS作用域
  2. 理解Angular的$apply()$digest()
  3. JavaScript事件处理中的新兴模式
  4. AngularJS作用域中的原型继承
  5. $watch等的文档

关于在AngularJS中掌握$watch的常见问题解答 (FAQ)

$watch在AngularJS中的主要目的是什么?

AngularJS中的$watch函数主要用于观察变量或表达式的值的变化。它是AngularJS作用域对象的一部分,用于监视变量或表达式的值的变化。检测到变化时,$watch函数会触发一个回调函数,该函数会在每次被监视的变量发生变化时执行。

$watch在AngularJS中是如何工作的?

AngularJS中的$watch函数通过比较被监视变量或表达式的旧值和新值来工作。它使用JavaScript的相等运算符(===)进行比较。如果新值与旧值不同,$watch函数将触发回调函数。

我如何在AngularJS中使用$watch

要在AngularJS中使用$watch,您需要在作用域对象上调用$watch方法,并向其传递两个参数:要监视的变量或表达式的名称,以及在被监视的变量发生变化时要执行的回调函数。这是一个示例:

<code class="language-javascript">app.controller('watchDemoCtrl', ['$scope', function($scope){
    $scope.letters = ['A','B','C'];
}]);</code>

$watch$apply在AngularJS中的区别是什么?

AngularJS中的$watch函数用于观察变量或表达式的变化,而$apply函数用于手动启动AngularJS摘要周期,该周期会检查被监视变量的任何变化并相应地更新视图。$apply函数通常在AngularJS上下文之外进行模型更改时使用,例如在DOM事件处理程序或setTimeout函数中。

我可以使用$watch在AngularJS中监视多个变量吗?

是的,您可以使用$watch在AngularJS中监视多个变量。您可以通过将变量名称数组传递给$watch函数来实现此目的。但是,请记住,监视多个变量可能会影响性能,因为$watch函数需要在每个摘要周期中检查所有被监视变量的变化。

我如何在AngularJS中停止监视$watch中的变量?

当您在AngularJS中调用$watch函数时,它会返回一个注销函数。您可以调用此函数来停止监视变量。这是一个示例:

<code class="language-javascript">$scope.$watch('letters', function (newValue, oldValue, scope) {
    // 对 $scope.letters 执行任何操作
});</code>

AngularJS中的$watchGroup是什么?

AngularJS中的$watchGroup函数用于监视一组表达式。它的工作方式类似于$watch函数,但它只在每个摘要周期触发一次回调函数,即使多个被监视的表达式发生了变化也是如此。这可以在监视多个表达式时提高性能。

AngularJS中的$watchCollection是什么?

AngularJS中的$watchCollection函数用于监视对象的属性或数组的元素。只要任何属性或元素发生变化,它就会触发回调函数,但与$watch不同,它不会深度监视对象或数组,这可以提高性能。

我可以在AngularJS指令中使用$watch吗?

是的,您可以在AngularJS指令中使用$watch。事实上,在指令中使用$watch来响应指令的属性或作用域变量的变化是一种常见的做法。

使用$watch在AngularJS中有哪些性能方面的考虑?

使用$watch在AngularJS中可能会影响性能,尤其是在监视许多变量或表达式时。这是因为$watch函数需要在每个摘要周期中检查所有被监视变量的变化。为了提高性能,请根据情况考虑使用$watchGroup$watchCollection,或限制被监视变量的数量。

以上是掌握$ Watch在Angularjs的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn