在使用AngulaJS编写应用时,我们经常需要做的一件事情就是对模型中的变量进行监视,并对其发生的变化做出相应的回应。AngularJS为我们提供了一个非常方便的$watch方法,它可以帮助我们在每个scope中监视其中的变量。下面是一个非常简单的例子:
<html ng-app='myApp'> <head> <meta charset="UTF-8"> <script src='../lib/angular.js'></script> </head> <body> <input ng-model='name' type='text'/> <div>change count: {{count}}</div> <script> angular.module('myApp',[]) .run(['$rootScope',function($rootScope){ $rootScope.count = 0; $rootScope.name = 'hcc'; $rootScope.$watch('name',function(){ $rootScope.count++; }) }]); </script> </body> </html>
上面的代码很简单,它用$watch来对$rootScope中的name进行监视,并在它发生变化的时候将$rootScope中的count属性增加1。因此,每当我们对name进行一次修改时,下面显示的count数字就会增加1。
在AngularJS内部,每当我们对ng-model绑定的name属性进行一次修改,AngularJS内部的$digest就会运行一次,并在运行结束之后检查我们使用$watch来监视的东西,如果和进行上一次$digest之前相比有了变化,则执行我们在其中绑定的处理函数。
然而,我们在实际运用中常常不只是对一个原始类型的属性进行监视,还可能对集合进行监视,如果你还记得Javascript中的五种基本类型(number, boolean, null, undefined, string)。对于原始类型,如果我们使用了一个赋值操作,则这个原始类型变量会“真正的”被进行一次复制,然而对于引用类型,在进行赋值时,仅仅时将赋值的变量指向了这个引用类型。在AngularJS的$watch方法中,对两者的操作也有不同之处。原始类型,就像我们上面例子中提到的$rootScope,没有什么特别之处,然而如果要对一个引用类型,尤其是在实际运用中常见的对象数组进行监视时,情况就不一样了。我们来看下面的例子:
<html ng-app='myApp'> <head> <meta charset="UTF-8"> <script src='../lib/angular.js'></script> </head> <body> <div hg-repeat='item in items'> <input ng-model='item.a'/><span>{{item.a}}</span> </div> <div>change count: {{count}}</div> <script> angular.module('myApp',[]) .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>
通过运行上面的代码,我们发现count的值始终是1,难道没有检测到吗?其实$watch()还有第三个参数,默认是false,我们称这种监视叫做“引用监视”。即“reference watch”,它的意思是只要监视的对象引用没有发生变化,就不算它发生了变化。具体来说,在上面的例子中,只要是items的引用没有发生变化,就算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++; }) }]);
对集合的操作,推荐使用这种方式。