马克·布朗 ( Mark Brown )对这篇文章进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
AngularJS提供了许多不同的选项,可以通过三种不同的“监视”方法来使用发布-订阅模式 。 它们每个都带有可以修改其行为的可选参数。
$watch
上的官方文档内容并不全面:毕竟,这个问题困扰着整个AngularJS v1。 即使是解释如何进行的在线资源充其量也是最多的。
因此,最终,对于给定情况,开发人员很难选择正确的方法。 对于AngularJS初学者来说尤其如此! 结果可能令人惊讶或无法预测,并且不可避免地会导致错误。
在本文中,我将假设您对AngularJS概念有所了解。 如果您觉得需要复习,则可能需要阅读$ scope , binding和$ apply和$ digest 。
检查您的理解
例如,观看数组第一个元素的最佳方法是什么? 假设我们在范围内声明了一个数组, $scope.letters = ['A','B','C'];
- 将
$scope.$watch('letters', function () {...});
当我们向数组添加元素时触发其回调? - 当我们改变它的第一个元素时会吗?
- 怎么样
$scope.$watch('letters[0]', function () {...});
? 它会工作相同还是更好? - 上面的数组元素是原始值:如果我们用相同的值替换第一个元素怎么办?
- 现在假设该数组代替了对象:会发生什么?
-
$watch
,$watchCollection
和$watchGroup
什么$watchGroup
?
如果您对所有这些问题感到困惑,请继续阅读。 我的目的是通过几个示例尽可能地阐明这一点,并逐步指导您。
$ scope。$ watch
让我们从$scope.$watch
。 这是所有手表功能的主力军:我们将看到的所有其他方法只是$watch
的便捷快捷方式。
$手表
现在,Angular的优点是,您可以显式地使用相同的机制来在控制器中执行由数据更改触发的复杂操作。 例如,您可以对一些可以响应以下内容而更改的数据设置观察者:
- 超时时间
- 用户界面
- 网络工作者执行的复杂异步计算
- Ajax通话
您可以只设置一个侦听器来处理任何数据更改,无论是什么原因引起的。
但是,您需要调用$scope.$watch
。
动手
让我们看一下$rootscope.watch()
的代码 。
这是它的签名: function(watchExp, listener, objectEquality, prettyPrintExpression)
。
详细来说,它的四个参数:
watchExp
正在监视的表达式。 它可以是一个函数或字符串,它在每个摘要周期进行评估。这里要注意的一个关键方面是, 如果将表达式作为函数求值,则该函数必须是幂等的。 换句话说,对于同一组输入,它应始终返回相同的输出。 如果不是这种情况,Angular将假定正在监视的数据已更改。 反过来,这意味着它将继续检测差异,并在摘要周期的每个迭代中调用侦听器。
listener
一个回调,在第一次设置手表时触发,然后在摘要周期中每次在其中检测到watchExp
的值更改时watchExp
。 安装程序的初始调用旨在存储表达式的初始值。objectEquality
仅当为true时,观察者将执行深度比较。 否则,它将执行浅表比较,即仅对引用进行比较。让我们以一个数组为例:
$scope.fruit = ["banana", "apple"];
。objectEquality == false
表示仅对水果字段的重新分配会产生对侦听器的调用。我们还需要检查“深度”是一个深层比较:我们将在后面进行讨论。
prettyPrintExpression
如果传递,它将覆盖监视表达式。 此参数不是要在对$watch()
常规调用中使用的; 它由表达式解析器在内部使用。注意 :如您所见,错误地传递第四个参数很容易导致意外结果。
现在我们准备回答导言中的一些问题。 看一下本节中的示例:
请参阅CodePen上的SitePoint ( @SitePoint )的Pen Angular $ watch演示– $ scope。$ watch() 。
请随时熟悉它们; 您可以直接比较行为差异,也可以按照文章中的顺序进行操作。
观察阵列
因此,您需要在您的作用域上观察Array的变化,但是“变化”是什么意思?
假设您的控制器看起来像这样:
app.controller('watchDemoCtrl', ['$scope', function($scope){
$scope.letters = ['A','B','C'];
}]);
一种选择是使用像这样的呼叫:
$scope.$watch('letters', function (newValue, oldValue, scope) {
//Do anything with $scope.letters
});
在上面的回调中, newValue
和oldValue
具有不言自明的含义,并且每次由$digest
循环调用时都是最新的。 scope
的含义也很直观,因为它引用了当前范围。
但是,重点是:何时将调用此侦听器? 实际上,您可以在letters
数组中添加,删除,替换元素,并且不会发生任何事情。 这是因为,默认情况下, $watch
假定您只希望引用相等 ,所以仅当您为$scope.letters
分配新值时,才会触发回调。
如果需要对数组的任何元素进行更改,则需要将true
作为您要监视的第三个参数传递(即,作为上述可选的objectEquality
参数的值)。
$scope.$watch('letters', function (newValue, oldValue, scope) {
//Do anything with $scope.letters
}, true);
观看对象
对于对象,交易不会改变:如果objectEquality
为false,则只需监视对该范围变量的任何重新分配,而如果为true,则每次更改对象中的元素时都会触发回调。
观看数组的第一个元素
通过观看带有objectEquality === true
的数组,每次触发回调时, newValue
和oldValue
将是整个数组的新旧值, 这 objectEquality === true
。 因此,您必须将它们相互比较才能了解实际发生的变化。
说,相反,您对更改数组中第一个元素(或第四个元素-原理相同)感兴趣。 好吧,由于Angular很棒,它可以让您做到这一点:并且您可以在作为第一个参数传递给$watch
的表达式中以自然的方式表达它:
$scope.$watch('letters[4]', function (newValue, oldValue, scope) {
//...
}, true);
如果数组只有2个元素怎么办? 没问题,在添加第四个元素之前不会触发您的回调。 好吧,好的,从技术上讲,它会在您设置手表时触发,然后仅在您添加第四个元素时触发。
如果您记录oldValue
那么在这种情况下,两次都将看到undefined
。 将其与观察现有元素时发生的情况进行比较:相反,在设置中,您仍然具有oldValue == undefined
。 因此, $watch
无法处理任何事情!
现在是一个更有趣的问题:我们是否需要在此处传递objectEquality === true
?
简短答案:对不起,没有简短答案。
这真的取决于:
- 在此示例中,由于我们正在处理原始值,因此不需要深入比较,因此可以省略
objectEquality
。 - 但是,假设我们有一个矩阵,例如
$scope.board = [[1, 2, 3], [4, 5, 6]];
,我们想观看第一行。 然后,当$scope.board[0][1] = 7
类的赋值更改它时,我们可能希望收到警告。
观看物体的视野
也许比观察数组中的任意元素有用,我们可以观察对象中的任意字段。 但这并不奇怪,对吗? 毕竟,JavaScript 中的数组是对象。
$scope.obj = {'a': 1, 'b': 2};
$scope.$watch('obj["a"]', function (newValue, oldValue, scope) {
// ...
});
深度比较有多深?
在这一点上,我们仍然需要澄清最后一个但至关重要的细节:如果我们需要观察一个复杂的嵌套对象,其中每个字段都是非原始值,会发生什么? 诸如树或图形之类的东西,或者只是一些JSON数据。
让我们来看看!
首先,我们需要观察一个对象:
$scope.obj = {
'a': 1,
'b': {
'ba': {
'bab': 2
},
'bb': [
{
'bb1a': 3,
'bb1b': 4
},
{
'bb2a': 5
}
]
}
};
让我们对整个对象进行监视:我假设到目前为止,在这种情况下,必须将objectEquality
设置为true
。
$scope.$watch('obj', function (newValue, oldValue, scope) {
//...
}, true);
问题是:当诸如$scope.b.bb[1].bb2a = 7;
类的赋值时,Angular是否会足够好让我们知道$scope.b.bb[1].bb2a = 7;
发生?
答案是:是的,幸运的是,它将(在先前的CodePen演示中查看)。
其他方法
$scope.$watchGroup
$watchGroup()
真的是一种不同的方法吗? 答案是否定的,不是。
$watchGroup()
是一个方便的快捷方式,它允许您通过传递watchExpressions
数组来设置多个具有相同回调的watchExpressions
。
传递的每个表达式都将使用标准的$scope.$watch()
方法进行$scope.$watch()
。
$scope.$watchGroup(['obj.a', 'obj.b.bb[1]', 'letters[2]'], function(newValues, oldValues, scope) {
//...
});
值得注意的是,使用$watchGroup
, newValues
和oldValues
将保存表达式值的列表,这些值确实发生了变化,并且保持了相同的值,它们的顺序与第一个传递的顺序相同。参数的数组。
如果您查看了此方法的文档,则可能已经注意到它没有使用objectEquality
选项。 这是因为它浅浅地监视表达式,并且仅对引用更改做出反应。
如果您在下面的$watchGroup()
演示中进行$watchGroup()
,您可能会为某些细节感到惊讶。 例如, unshift
至少在一定程度上会导致监听器被调用:这是因为在将表达式列表传递给$watchGroup
,其中的任何触发都会导致执行回调。
请参阅CodePen上的PenAngular $ watch演示– $ scope。$ watchGroup by SitePoint( @SitePoint )。
另外,请注意,对$scope.obj.b
's'子字段进行任何更改都不会产生任何更新-仅向b
字段本身分配新值即可。
$scope.$watchCollection
这是观察数组或对象的另一个方便的快捷方式。 对于数组,当替换,删除或添加任何元素时,将调用侦听器。 对于对象,更改任何属性时。 同样, $watchCollection()
不允许objectEquality
,因此它只会浅表objectEquality
元素/字段,并且不会对其子字段的更改做出反应。
请参阅CodePen上的SitePoint ( @SitePoint )的Pen Angular $ watch演示– $ scope。$ watchCollection() 。
结论
希望这些示例可以帮助您发现此Angular功能的强大功能,并了解使用正确选项的重要性。
随意分叉CodePens并在不同的上下文中尝试方法,不要忘记将您的反馈留在注释区域!
如果您想对本文中讨论的一些概念有更深入的了解,请参考以下一些建议: