谈谈angularjs中$watch,$apply,$digest

http://my.oschina.net/Nealyang/blog/552069

摘要
angularJs中$watch,$apply和$digest的使用和原理

大家都是到angularJS中非常屌的一个性能,数据双向绑定,这就意味着view中的数据发生任何变化的时候,这个变化也会相应的反映到scope上,也就是说scope的模型会动态更新。所以有时候我们的需求可能是要监控摸个model的变化,下面就简单的说下这个操作以及实现原理

$watch
$watch可以很方便且简单的监控一个model的变化,举个栗子:

<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>

上面这个代码就是用来监控name的变化的,每次当我们在input输入框中输入一个值的时候,$rootScope中的count就会对应的+1;

在angularJS的内部,每当我们对name的值进行修改的时候,angularJS内部中的$digest就会被调用一次,(下面在详细的说$digest的原理),并在运行结束之后检查我们用$watch来监控的模型,如何和上一次执行$digest之前相比发生变化了,则执行$watch中的回调函数。

然而!!!在我们实际的开发中,仅仅实现对一个原始类型的数据监控是远远不能够满足所需的,对于原始类型的数据,如果我们使用了一次赋值操作,则这个原始类型的数据变量会真正的别赋值一次,然而对于引用类型的变量,进行赋值时,仅仅是将赋值的变量指向了这个引用类型。
可以看下下面的栗子:

<html>
<head>
    <script src='./lib.angular.min.js'></script>
</head>
<body ng-app='watch'>
    <div ng-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>

在这个栗子中我们就会发现,不管我们怎么改变其中的值,count都不会发生变化的。而这个就是我们上面说那样,在说明这个之前,我们在说明下$watch的第三个参数,一般$watch函数的前两个参数是必传的(监控对象,回调函数),第三个参数默认为false,这样的话我们进行的监控叫做引用监控,这个意思就是监控对象的应用没有发生变化的时候就不算对象发生了变化,具体的来说,上面的例子,就算items的属性发生了变化,只要items的引用没有发生变化,$watch就都当做没有看见,但是比如讲一个数组赋值给items时,这个时候$watch就看不下去了(给你阳光你就灿烂了还)。
相反,如果第三个参数设置为true的时候,那么我们的监控叫做“全等监控”,此时的$watch的要求就是比较苛刻了,只要他监控的对象有一点点变化时,$watch就会跳出来,卧槽!你居然还敢动!!!

当然值得提一下的是:为什么第三个参数加个true,这么方便了我们还不加呢?!当然是牵涉到性能的问题啦!全等监控运行起来的时候是先监控到整个对象,然后在每一次吧$diges跑起来之前先用angualr.copy()将整个对象先拷贝一遍之后再调用angular.equal()方法来进行比较,所以这一监控可能会消耗大量的资源!

在angularJS 1.1.4又出来了一个$watchCollection()方法,专门来监控数组集合的,他的性能介于引用监控和全等监控之间,它不会对数组的每一项内容 进行监控,而是当数组的pop和push时候做出反应。具体的栗子咱就不多赘述了。

下面谈一谈$apply
大家都知道他的作用是把改变同步绑定到界面上,但是它为什么存在呢?什么时候需要用它呢?什么时候又不需要呢?
那么我们首先说一下angular是如何进行数据双向绑定的吧。
要知道一个变量变了,方法无非就两种
1、 通过固定的接口,比如set,get方法,通过set设置变量的值,set被调用时做个比较就可以,但是这个方法和复杂!
2、 脏检查,将某一个对象复制一份快照,在某个时间,比较现在对象与快照的值。很明显,这个方法要复制两份对象,而且要遍历对象,比较每一个属性。对!这样的确有性能问题!但是angular就是用这个的~
但是人家angular的脏检查不是对所有对象进行检查,只是当对象绑定到html中,该对象才复合检查对象(watcher),同理,angular对属性的脏检查也是如此。
看下watcher对象的源代码我们就知道了:

watcher = {
 fn: listener, //监听回调函数
 last: initWatchVal, //上一状态值
 getget//取得监听的值
 exp: watchExp, //监听表达式 
eq: !!objectEquality //要不要比较引用
 };

那么我们什么时候去进行脏检查呢?
脏检查的点是在函数执行完之后,但是不标明异步调用也执行完毕,如果我们的功能是异步的,那么我们会发现我们的改变并没有更新到DOM上。
举个栗子:

<!-- lang: js -->
 function Ctrl($scope) {
 $scope.message = "Waiting 2000ms for update"setTimeout(function () {
 $scope.message = "Timeout called!"; // AngularJS unaware of update to $scope }, 2000); }

简单说dom上永远都不会显示Timeout called
当然,这个就是我们$apply的应用场景了,调用它,手动触发脏检查,举个例子:angularJs提供了$timeout,为什么咱有了setTimeout还要提供这个呢?就是应为$timeout异步完成后,angularJs会自动触发$apply

下面说下$apply的使用注意事项吧
在一个具有$apply的环境中使用apply,会抛出异常来的
所以如果说我们的代码不在$apply环境中,结构是异步返回的,我们就需要手动触发$apply
Apply接受一个函数作为参数,函数中别绑定的对象会被脏检查,function不能是异步的!当然,当apply函数的参数为空的是时候,它会把当前作用域中所有的脏对象都检查一遍。浪费性能!

然而这个脏检查有事怎么检查的呢??
好吧,这个就说到$digest函数了
$apply被调用后最终都会触发$digest()
在调用了 $scope.$digest() 后, $digest 循环就开始了。假设你在一个 ng-click 指令对应的 handler 函数中更改了 scope 中的一条数据,此时 AngularJS 会自动地通过调用 $digest() 来触发一轮 $digest 循环。当 $digest 循环开始后,它会触发每个 watcher 。这些 watchers 会检查 scope 中的当前 model 值是否和上一次计算得到的 model 值不同。如果不同,那么对应的回调函数会被执行。调用该函数的结果,就是 view 中的表达式内容 ( 译注:诸如 {{ aModel }}) 会被更新。除了 ng-click 指令,还有一些其它的 built-in 指令以及服务来让你更改 models( 比如 ng-model , $timeout 等 ) 和自动触发一次 $digest 循环

暂且就说这么多吧,说多了也迷糊,很多都是需要自己去实际应用和体会的


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值