谈谈AngularJS中的$watch
发布在用Angular开发web应用2015年8月7日view:42469IgoriAssow
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

enter image description here

在使用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。


如果你觉得本文对你有帮助,请点击为我提供赞助

评论
发表评论
1年前
赞了此文章!
2年前

请问写监听items的时候,写第三个参数为true的时候也没起作用呢?

{{item.a}}
<div>change count: {{count}}</div>
<script src="../angular.min.js"></script>
<script>
    var myapp = angular.module('watchAPP',[]);
    myapp.run(['$rootScope',function($rootScope){
        $rootScope.count = 0;
        $rootScope.items = [
            { "a": 1 },
            { "a": 2 },
            { "a": 3 },
            { "a": 4 }
        ];
        $rootScope.$watchCollection('items',function(newvalue,oldvalue){
            console.log(newvalue);
            console.log(oldvalue);
            $rootScope.count++;
        },true);//加上true之后没起作用
    }]);
</script>
2年前

watch方法用法 $watch(watchFn,watchAction,deepWatch) watchFn:angular表达式或函数的字符串 watchAction(newValue,oldValue,scope):watchFn发生变化会被调用 deepWatch:可选的布尔值命令检查被监控的对象的每个属性是否发生变化 $watch会返回一个函数,想要注销这个watch可以使用函数

3年前
赞了此文章!
3年前
添加了一枚【评注】:对sql千万级运行时,会有些吃不消,但对状态修改还是有用的
4年前
添加了一枚【评注】:111
4年前

看看

4年前

@多米8 我也是这样

4年前
添加了一枚【评注】:这是怎么做的?
4年前
添加了一枚【评注】:11
4年前
添加了一枚【评注】:youdianyisi
4年前

scope.$watch(function(){ return busyService.isBusy; },function(newValue){ alert(busyService.isBusy); scope.isBusy=newValue; });

        scope.$watch(busyService.isBusy,function(newValue,oldValue){
            alert(busyService.isBusy);
            scope.isBusy=newValue;
        });

您好,我想问下这两个不同的写法,为什么我的第二个写法只有在初始化的时候被调用了一次(之后就没再被调用过),而第一个可行

4年前

写的很好,赞一个!

WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
用Angular开发web应用

讲述Angular开发框架的基础知识,帮助读者快速学习并深入理解Angular的开发理念和具体应用方式

我的收藏