[AngularJS面面观] 12. scope中的watch机制---第三种策略$watchCollection

如果你刚刚入门angular,你或许还在惊叹于angular的双向绑定是多么的方便,你也许在庆幸未来的前端代码中再也不会出现那么多繁琐的DOM操作了。

但是,一旦你的应用程序随着业务的复杂而复杂,你就会发现你手头的那些angular的知识似乎开始不够用了。为什么绑定的数据没有生效?为什么应用的速度越来越慢?为什么会出现莫名其妙的infinite digest异常?所以你开始尝试进阶,尝试弄清楚在数据绑定这个现象后面到底发生了什么。

相信能顺着前面数十篇文章看到这里的同学们,一定对angular是真爱吧。我们分析了scope中的digest循环的具体逻辑和很多细节,也见识到了angular中建立在继承树型结构之上的事件机制。本文继续聊聊关于scope的最后一个话题,watch策略。

基于引用以及值的watch策略

由scope中的$watch方法定义得到的watcher是digest循环中的工作单元。所以,神奇的双向绑定这一技术的根本之根本也就是watcher。前面已经介绍了watch机制的两种策略:

  1. 比对引用
  2. 比对

$watch方法提供了第三个参数用来让你选择是使用比对引用或是比对值的方式:

/*
* 第三个参数objectEquality的含义:使用对象值比对,而不是引用比对
*/
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression)

由于在angular中被双向绑定多半都是number,string这类基本类型(Primitive),因此引用比对就是默认的选项。如果你需要比较对象(Object),比如数组,字典对象等,那么就需要将上述$watch方法的第三个参数置为true。

定义了watcher后,那么在digest循环中就需要执行真正的比对操作了,判断一个watcher是否”脏”了的核心判断条件如下:

// 用于判定一个watcher是否dirty的条件
if ((value = get(current)) !== (last = watch.last) &&
      // 上述的objectEquality即为这里的watch.eq
      !(watch.eq
        ? equals(value, last)
        : (typeof value === 'number' && typeof last === 'number'
           && isNaN(value) && isNaN(last)))) {
    dirty = true;
  // 调用watcher上的listener
  // ......
 }

这里利用了&&操作符的”短路”特性,首先执行(value = get(current)) !== (last = watch.last),如果执行的结果为false,也就是在当前值和前值引用相等时,则判定该watcher不是”脏”的,因此也就不必要执行后续的逻辑了。由于watch的对象以基本值(Primitive)类型居多,因此这样做能够减少不必要的判断操作。当引用相同时,直接pass。只有当引用都不同时,才有可能需要”特别照顾”。if中的第二个子判断又出现了分支情况:

当需要进行值比对时,会调用angular定义的一个全局函数equals进行逐字段的递归比较过程,它能够确保被比较的两个对象真的是完全一致的,无论这个对象中嵌套了多少层对象。而第二个看似很复杂的只是为了应对JavaScript中一个很神奇的定义:NaN === NaN // return falseNaN不等于它自己,而且NaN的类型是number。这算是JavaScript的一个槽点吧,不过也没什么难的,遇到了仔细处理就好了。

这里还有一段示例程序,可以直接复制到本地的server上运行看看效果:

<html ng-app="testApp">
<head>
  <meta charset="UTF-8">
  <title>Watch Mechanism</title>
  <script src="//cdn.bootcss.com/angular.js/1.5.7/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
</body>
<script>
  var mod = angular.module('testApp', []);
  mod.controller('mainCtrl', function($scope, $interval) {
     
    $scope.obj = { 'id': 1 };

    $scope.$watch(function(scope) {
      return scope.obj; },
      function(newValue, oldValue, scope) {
     
        console.log(newValue, oldValue);
      });
      // }, true);

    $interval(function() {
     
      console.log('digest triggered');
      $scope.obj.id += 1;
    }, 1000);
  });
</script>
</html>

当在$watch中不传第三个参数的时候,可以看到newValueoldValue只在控制台中被打印了一次。当传入第三个参数true的时候,可以看到每个一秒都会打印一次。

当初看到这里的时候,我有一个疑问。既然判断条件首先会判断新值和旧值的引用是否相同,再决定是否进行深度的值比对。那么上面的实例中每次改变的只是对象中的一个字段,对象本身并没有改变啊,也就意味着引用也没有被改变?

确实这个疑问大概困扰了一会。直到我继续阅读当数据”脏”了的处理逻辑时,才恍然大悟:

watch.last = watch.eq ? copy(value, null) : value;

这里调用了定义在Angular.js中的另一个工具方法copy。现在还不着急介绍这些工具方法,简而言之这个方法会创建出一个和源对象一模一样的新对象并赋予旧值。注意这个”新”字,这也就意味着新值和旧值在每次执行值比对的时候,都是两个素昧平生的对象。因此就能够顺利地通过第一层引用判断。

以上就是我们已经见过好多次的两种watch策略。那么这第三种策略到底是何方”黑科技”,有什么存在意义和价值呢?

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值