引自:
在使用 AngulaJS 编写应用时,我们经常需要做的一件事情就是对模型中的变量进行监视,并对其发生的变化做出相应的回应.AngularJS 为我们提供了一个非常方便的 $watch 方法,它可以帮助我们在每个 scope 中监视其中的变量.下面是一个非常简单的例子:
<html>
<head>
<script src='./lib.angular.min.js'>
</script>
</head>
<body ng-app='watch'>
<input ng-model='name' type='text' />
<div>
change count: {{count}}
</div>
<script>
angular.module('watch', []).run(['$rootScope',
function($rootScope) {
$rootScope.count = 0;
$rootScope.name = 'Alfred';
$rootScope.$watch('name',
function() {
$rootScope.count++;
})
}]);
</script>
</body>
</html>
上面的这段代码非常简单,它用 $watch 来对 $rootScope 中的 name 进行监视,并在它发生变化的时候将 $rootScope 中的 count 属性增加 1.因此,每当我们对 name 进行一次修改时,下面显示的 change count 数字就会增加 1.
在 AngularJS 内部,每当我们对 ng-model 绑定的 name 属性进行一次修改,AngularJS 内部的 $digest 就会运行一次,并在运行结束之后检查我们使用 $watch 来监视的东西,如果和进行上一次 $digest 之前相比有了变化,则执行我们在其中绑定的处理函数.
然而,我们在实际运用中常常不只是对一个原始类型的属性进行监视,如果你还记得 Javascript 中的六种基本类型,你一定会记得原始类型(数字,字符串)和引用类型的区别.对于原始类型,如果我们使用了一个赋值操作,则这个原始类型变量会 "真正的" 被进行一次复制,然而对于引用类型,在进行赋值时,仅仅时将赋值的变量指向了这个引用类型.在 AngularJS 的 $watch 方法中,对两者的操作也有不同之处.原始类型,就像我们上面例子中提到的 $rootScope,没有什么特别之处,然而如果要对一个引用类型,尤其是在实际运用中常见的对象数组进行监视时,情况就不一样了.我们来看下面的例子:
<html>
<head>
<script src='./lib.angular.min.js'>
</script>
</head>
<body ng-app='watch'>
<div hg-repeat='item in items'>
<input ng-model='item.a' />
<span>
{{item.a}}
</span>
</div>
<div>
change count: {{count}}
</div>
<script>
angular.module('watch', []).run(['$rootScope',
function($rootScope) {
$rootScope.count = 0;
$rootScope.items = [{
"a": 1
},
{
"a": 2
},
{
"a": 3
},
{
"a": 4
}] $rootScope.$watch('items',
function() {
$rootScope.count++;
})
}]);
</script>
</body>
</html>
此时,如果我们对四个 input 中的 a 进行改变时,我们会发现,count 的值依然是 0.这是怎么回事?难道没有 $watch 失灵了吗?
正如我们前面所说的,$watch 在对待原始类型和引用类型会有不同的处理方式,这就要首先说一说 $watch 函数的第三个参数.在前面的例子中,我们知道,$watch 函数有接收两个参数,第一个参数是需要监视的对象,第二个参数是在监视对象发生变化时需要调用的函数,实际上 $watch 还有第三个参数,它在默认情况下是 false.在默认情况下,即不显式指明第三个参数或者将其指明为 false 时,我们进行的监视叫做 "引用监视".引用监视的原词的 "reference watch",它的意思是只要监视的对象引用没有发生变化,就不算它发生了变化.具体来说,在上面的例子中,只要是 items 的引用没有发生变化,就算 items 中的一些属性发生了变化,$watch 也会当做没有看见.那么在什么时候算是引用发生了变化呢?比如说将一个新的数组 newItems 赋值给 items,此时 $watch 才会站出来说:"你变了!你再也不是以前我认识的那个和我一起看星星看月亮聊人生理想并且教会我什么叫做爱的人了!"
相反,如果我们将 $watch 的第三个变量设置为 true,那么此时我们进行的监视叫做 "全等监视",原词是 "equality watch".此时,$watch 就像是一个醋意十足的恋人,只要看他的对象有一点风吹草动,马上就跳出来,大喊大叫:"你冷酷你无情,你无理取闹.就算我冷酷,我无情,我无理取闹,也没有你冷酷你无情,你无理取闹!尔康.....".
因此,有同学就会问了,既然全等监视这么好,那么我们为什么不直接用全等监视呢?当然,任何事情都有好的坏的两个方面,全等监视固然是好,但是它在运行时需要先遍历整个监视对象,然后在每次 $digest 之前使用 angular.copy() 将整个对象深拷贝一遍然后在运行之后用 angular.equal() 将前后的对象进行对比,上面的例子中因为 items 比较简单,因此可能性能上不会有什么差别,但是到了实际生产时,我们要面对的数据千千万万,可能因为全等监视这一个设置就会消耗大量的资源,让应用停滞不前.因此这就需要我们在使用时进行权衡,究竟应该使用哪一种监视方式.
除了上面提到的两种方式之外,在 angular 1.1.4 版本之后,添加了一个 $watchCollection() 方法来针对数组(也就是集合)进行监视,它的性能介于全等监视和引用监视二者之间,即它并不会对数组中每一项的属性进行监视,但是可以对数组的项目的增减做出反应.比如还是上面的例子:
$rootScope.items = [{
"a": 1
},
{
"a": 2
},
{
"a": 3
},
{
"a": 4
}] $rootScope.$watchCollection('items',
function() {
$rootScope.count++;
})
}]);
如果改变了 items[0] 的 a 属性值,$watch 并不会做出反应,但是如果我们在 items 上 push 或者 pop 了一个项目,$watch 就会开始行动了.
关于 AngularJS 中 $watch 部分就讲这么多,还是那句话 "最好的学习方式就是实践".多 coding,多犯错,一定能学好 AngularJS.
来源: