我知道 Watchers
和 Observers
都会在 AngularJS 中的 $scope
发生变化时立即计算。但无法理解两者之间究竟有什么区别。
我最初的理解是 Observers
是为角度表达式计算的,这些表达式是 HTML 端的条件,在执行 $scope.$watch()
函数时执行 Watchers
。我在想正确吗?
$observe() 是 Attributes 对象上的一个方法,因此只能用于观察/观察 DOM 属性的值变化。它仅在指令内部使用/调用。当您需要观察/观察包含插值的 DOM 属性(即 {{}} 的)时,请使用 $observe。
例如,attr1="Name: {{name}}"
,然后在指令中:attrs.$observe('attr1', ...)
。
(如果您尝试 scope.$watch(attrs.attr1, ...)
,它会因为 {{}} 而不起作用——您会得到 undefined
。)使用 $watch 来完成其他所有操作。
$watch() 更复杂。它可以观察/观察“表达式”,其中表达式可以是函数或字符串。如果表达式是一个字符串,它会被 $parse'd(即,评估为 Angular expression)到一个函数中。 (每个摘要循环都会调用此函数。)字符串表达式不能包含 {{}}。 $watch 是 Scope 对象上的一个方法,因此可以在您可以访问范围对象的任何地方使用/调用它,因此在
控制器——任何控制器——通过 ng-view、ng-controller 或指令控制器创建的控制器
指令中的链接函数,因为它也可以访问范围
因为字符串被评估为 Angular 表达式,所以当你想观察/观察模型/范围属性时,经常使用 $watch。例如,attr1="myModel.some_prop"
,然后在控制器或链接函数中:scope.$watch('myModel.some_prop', ...)
或 scope.$watch(attrs.attr1, ...)
(或 scope.$watch(attrs['attr1'], ...)
)。
(如果您尝试 attrs.$observe('attr1')
,您将得到字符串 myModel.some_prop
,这可能不是您想要的。)
正如对@PrimosK 答案的评论中所讨论的,所有 $observes 和 $watches 每隔 digest cycle 都会检查一次。
具有隔离范围的指令更复杂。如果使用 '@' 语法,您可以 $observe 或 $watch 包含插值的 DOM 属性(即 {{}} 的)。 (它与 $watch 一起工作的原因是因为 '@' 语法为我们执行 interpolation,因此 $watch 看到一个没有 {{}} 的字符串。)为了更容易记住何时使用哪个,我建议在这种情况下也使用 $observe。
为了帮助测试所有这些,我编写了一个定义两个指令的 Plunker。一个 (d1
) 不创建新范围,另一个 (d2
) 创建隔离范围。每个指令都有相同的六个属性。每个属性都是 $observe'd 和 $watch'ed。
<div d1 attr1="{{prop1}}-test" attr2="prop2" attr3="33" attr4="'a_string'"
attr5="a_string" attr6="{{1+aNumber}}"></div>
查看控制台日志,可以看到 $observe 和 $watch 在链接函数中的区别。然后单击链接,查看单击处理程序所做的属性更改触发了哪些 $observes 和 $watches。
请注意,当链接函数运行时,尚未评估任何包含 {{}} 的属性(因此,如果您尝试检查属性,您将得到 undefined
)。查看插值的唯一方法是使用 $observe(如果使用带有 '@' 的隔离范围,则使用 $watch)。因此,获取这些属性的值是一个异步操作。 (这就是我们需要 $observe 和 $watch 函数的原因。)
有时你不需要$observe 或$watch。例如,如果您的属性包含一个数字或布尔值(不是字符串),只需对其进行一次评估:attr1="22"
,然后在您的链接函数中:var count = scope.$eval(attrs.attr1)
。如果它只是一个常量字符串——attr1="my string"
——那么只需在你的指令中使用 attrs.attr1
(不需要 $eval())。
另请参阅 Vojta's google group post 关于 $watch 表达式。
如果我正确理解您的问题,您会问如果您使用 $watch
注册侦听器回调或使用 $observe
注册监听器回调有什么区别。
执行 $digest
时会触发注册到 $watch
的回调。
使用 $observe
注册的回调在包含插值的属性(例如 attr="{{notJetInterpolated}}"
)的值更改时被调用。
在指令内部,您可以以非常相似的方式使用它们:
attrs.$observe('attrYouWatch', function() {
// body
});
或者
scope.$watch(attrs['attrYouWatch'], function() {
// body
});
$digest
阶段,因此可以安全地假设 $observe
回调将在 $digest
中调用。并且 $watch
回调也将在 $digest
中调用,但无论何时更改值。我认为他们做的工作完全相同:“观察表达式,调用回调值变化”。关键字差异可能只是语法糖,以免使开发人员感到困惑。
我认为这很明显:
$observe 用于指令的链接功能。
$watch 在作用域上用于观察其值的任何变化。
请记住:两个函数都有两个参数,
$observe/$watch(value : string, callback : function);
value :始终是对被监视元素的字符串引用(范围变量的名称或要监视的指令属性的名称)
callback : 表单函数(oldValue, newValue)要执行的函数
我制作了一个plunker,因此您实际上可以掌握它们的使用情况。我使用了变色龙的类比来使其更容易描绘。
为什么 $observe 与 $watch 不同?
watchExpression 在每个 digest() 循环中被评估并与前一个值进行比较,如果 watchExpression 值发生变化,则调用 watch 函数。
$observe 专门用于观察插值。如果指令的属性值被插值,例如 dir-attr="{{ scopeVar }}"
,则仅在设置插值时调用观察函数(因此当 $digest 已经确定需要进行更新时)。基本上已经有一个插值的观察者,并且 $observe 函数捎带了它。
见 $observe & $设置在 compile.js
不定期副业成功案例分享
ng-src/ng-href
使用attr.$observe
而不是scope.$watch
吗?@
语法时可以互换。我相信没有性能差异(但我没有查看实际的源代码)。