双向绑定的三个重要方法:

  • $scope.$apply()
  • $scope.$digest()
  • $scope.$watch()

一、$scope.$watch()

  我理解的$watch就是将对某个数据的监听器对象存储在$scope下。当给$watch指定如下两个函数,就可以创建一个监听器:

  • 一个监控函数,我们通常传进去的是一个表达式,比如说“user.firstName”,但框架本身实际上是调用了一个函数,返回指定所关注的那部分数据。
  • 一个监听函数,用于在数据变更的时候接受提示。

  为了实现$watch,我们需要存储监听器对象。在Scope构造函数上添加一个数组:

  1. function Scope() {
  2. this.$$watchers = [];
  3. }

$$在angular中表示这个变量被当作私有的来考虑,不应当在外部代码中调用。

  现在我们正式定义$watch()方法,源代码如下所示:

  1. $watch: function(watchExp, listener, objectEquality) {
  2. var scope = this,
  3. get = compileToFn(watchExp, 'watch'),
  4. array = scope.$$watchers,
  5. watcher = {
  6. fn: listener,
  7. last: initWatchVal,
  8. get: get,
  9. exp: watchExp,
  10. eq: !!objectEquality
  11. };
  12.  
  13. // in the case user pass string, we need to compile it, do we really need this ?
  14. if (!isFunction(listener)) {
  15. var listenFn = compileToFn(listener || noop, 'listener');
  16. watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
  17. }
  18.  
  19. if (typeof watchExp == 'string' && get.constant) {
  20. var originalFn = watcher.fn;
  21. watcher.fn = function(newVal, oldVal, scope) {
  22. originalFn.call(this, newVal, oldVal, scope);
  23. arrayRemove(array, watcher);
  24. };
  25. }
  26.  
  27. if (!array) {
  28. array = scope.$$watchers = [];
  29. }
  30. // we use unshift since we use a while loop in $digest for speed.
  31. // the while loop reads in reverse order.
  32. array.unshift(watcher);
  33.  
  34. return function() {
  35. arrayRemove(array, watcher);
  36. };
  37. },

其中$$watchers就是wo我们上述的scope中存储监听器的数组,$watch()通过unshift()方法将监听器对象加入数组。

二、$scope.$digest()

$digest函数的作用是简而言之就是作用域上遍历所有监听器,也就是$scope.$$watchers,调用每个监听器对象下的监控函数,并且比较它返回的值和上一次返回值的差异。如果不相同,监听器就是脏的,它的监听函数就应当被调用。源代码如下所示:

  1. $digest: function() {
  2. var watch, value, last,
  3. watchers,
  4. asyncQueue = this.$$asyncQueue,
  5. postDigestQueue = this.$$postDigestQueue,
  6. length,
  7. dirty, ttl = TTL,
  8. next, current, target = this,
  9. watchLog = [],
  10. logIdx, logMsg, asyncTask;
  11.  
  12. beginPhase('$digest');
  13.  
  14. do { // "while dirty" loop
  15. dirty = false;
  16. current = target;
  17.  
  18. while(asyncQueue.length) {
  19. try {
  20. asyncTask = asyncQueue.shift();
  21. asyncTask.scope.$eval(asyncTask.expression);
  22. } catch (e) {
  23. $exceptionHandler(e);
  24. }
  25. }
  26.  
  27. do { // "traverse the scopes" loop
  28. if ((watchers = current.$$watchers)) {
  29. // process our watches
  30. length = watchers.length;
  31. while (length--) {
  32. try {
  33. watch = watchers[length];
  34. // Most common watches are on primitives, in which case we can short
  35. // circuit it with === operator, only when === fails do we use .equals
  36. if (watch && (value = watch.get(current)) !== (last = watch.last) &&
  37. !(watch.eq
  38. ? equals(value, last)
  39. : (typeof value == 'number' && typeof last == 'number'
  40. && isNaN(value) && isNaN(last)))) {
  41. dirty = true;
  42. watch.last = watch.eq ? copy(value) : value;
  43. watch.fn(value, ((last === initWatchVal) ? value : last), current);
  44. if (ttl < 5) {
  45. logIdx = 4 - ttl;
  46. if (!watchLog[logIdx]) watchLog[logIdx] = [];
  47. logMsg = (isFunction(watch.exp))
  48. ? 'fn: ' + (watch.exp.name || watch.exp.toString())
  49. : watch.exp;
  50. logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
  51. watchLog[logIdx].push(logMsg);
  52. }
  53. }
  54. } catch (e) {
  55. $exceptionHandler(e);
  56. }
  57. }
  58. }
  59.  
  60. // Insanity Warning: scope depth-first traversal
  61. // yes, this code is a bit crazy, but it works and we have tests to prove it!
  62. // this piece should be kept in sync with the traversal in $broadcast
  63. if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
  64. while(current !== target && !(next = current.$$nextSibling)) {
  65. current = current.$parent;
  66. }
  67. }
  68. } while ((current = next));
  69.  
  70. if(dirty && !(ttl--)) {
  71. clearPhase();
  72. throw $rootScopeMinErr('infdig',
  73. '{0} $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: {1}',
  74. TTL, toJson(watchLog));
  75. }
  76. } while (dirty || asyncQueue.length);
  77.  
  78. clearPhase();
  79.  
  80. while(postDigestQueue.length) {
  81. try {
  82. postDigestQueue.shift()();
  83. } catch (e) {
  84. $exceptionHandler(e);
  85. }
  86. }
  87. },

三、$scope.$apply()

  $apply使用函数作参数,它用$eval执行这个函数,然后通过$digest触发digest循环。源代码如下所示:

  1. $apply: function(expr) {
  2. try {
  3. beginPhase('$apply');
  4. return this.$eval(expr);
  5. } catch (e) {
  6. $exceptionHandler(e);
  7. } finally {
  8. clearPhase();
  9. try {
  10. $rootScope.$digest();
  11. } catch (e) {
  12. $exceptionHandler(e);
  13. throw e;
  14. }
  15. }
  16. },

  可以看到,在apply方法里其实是调用了digest方法的,那么为什么要多增加一个apply来调用digest呢,可以看到这段代码中并没有直接调用digest而是首先进行了对expr的检验,也就是eval方法,这个方法如果校验不通过,是会抛出异常的,而angular并不推荐外部直接调用digest,所以就增加了apply方法来间接调用。

  利用$apply(),我们可以执行一些与Angular无关的代码,这些代码也还是可以改变作用域上的东西,$apply可以保证作用域上的监听器可以检测这些变更。

四、何时执行和跳出digest loop呢?

  引用网上一张图来解释

资料引用自:http://my.oschina.net/brant/blog/419641

AngularJs双向绑定详解的更多相关文章

  1. AngularJS模块的详解

    AngularJS模块的详解 在讲angularjs的模块之前,我们先介绍一下angular的一些知识点: AngularJS是纯客户端技术,完全用Javascript编写的.它使用的是网页开发的常规 ...

  2. AngularJS开发指南6:AngularJS表单详解

    表单控件(input, select, textarea )是用来获取用户输入的.表单则是一组有联系的表单控件的集合. 用户能通过表单和表单控件提供验证的服务,知道自己的输入是否合法.这样能让用户交互 ...

  3. Angularjs 双向绑定机制解析

    文章转自:http://www.2cto.com/kf/201408/327594.html AngularJs 的元素与模型双向绑定依赖于循环检测它们之间的值,这种做法叫做脏检测,这几天研究了一下其 ...

  4. AngularJs自定义指令详解(5) - link

    在指令中操作DOM,我们需要link参数,这参数要求声明一个函数,称之为链接函数. 写法: link: function(scope, element, attrs) { // 在这里操作DOM} 如 ...

  5. AngularJS指令的详解

    指令作为AngularJS中最为重要的部分,所以这个框架本身也是自带了比较多的的指令,但是在开发中,这些指令通常不能满足我们的需要,所以我们也是需要自定义一些指令的.指令是我们用来扩展浏览器能力的技术 ...

  6. AngularJs自定义指令详解(3) - scope

    我们之所以要定义指令,目的是重用指令.假设有这么一个应用场景:在同一个html里使用了两次my-directive,第一个my-directive要展示的是Hello World,第二个my-dire ...

  7. AngularJS开发指南14:AngularJS的服务详解

    服务是一种由服务器端带到客户端的特性,它由来已久.AngularJS应用中的服务是一些用依赖注入捆绑在一起的可替换的对象.服务是最常和依赖注入一起用的,它也是AngularJS中的关键特性. 接下来, ...

  8. angularjs directive 实例 详解

    前面提到了angularjs的factory,service,provider,这个可以理解成php的model,这种model是不带html的,今天所说的directive,也可以理解成php的mo ...

  9. angularjs的directive详解

    Directive(指令)笔者认为是AngularJ非常强大而有有用的功能之一.它就相当于为我们写了公共的自定义DOM元素或CLASS属性或ATTR属性,并且它不只是单单如此,你还可以在它的基础上来操 ...

随机推荐

  1. iOS隐藏导航条1px的底部横线

    第二种方法:1)声明UIImageView变量,存储底部横线 @implementation MyViewController { UIImageView *navBarHairlineImageVi ...

  2. jmeter 各种配置修修改(后续增加)

    1.修改物理内存  使用jmeter进行压力测试时遇到一段时间后报内存溢出outfmenmory错误,导致jmeter卡死了,先尝试在jmeter.bat中增加了JVM_ARGS="-Xmx ...

  3. OpenGL中的需要注意的细节问题

    OpenGL中的需要注意的细节问题 1. 虽然我们使用Windows的BMP文件作为纹理时,一般是蓝色的像素在最前,其真实的格式为GL_BGR而不是GL_RGB,在数据的顺序上有所 不同,但因为同样是 ...

  4. task4:结对项目-词频统计

    结对人:周楠 思路:利用TreeMap实现key字典序,然后输出到LinkedList,然后用Comparator,实现字典值从大到小排序,但是key实现值相同的key字典序的想出的实现方法,但是一直 ...

  5. Linux系统的运行级的概念

    Linux OS 将操作 环境分为以下7个等级,即 0:关机 1:单用户模式(单用户.无网络) 2:无网络支持的多用户模式(多用户.无网络) 3:有网络支持的多用户模式(多用户.有网络) 4:保留,未 ...

  6. 基于Struts2开发学生信息管理系统 源码

    开发环境:    Windows操作系统开发工具: Eclipse+Jdk+Tomcat+MYSQL数据库 运行效果图: 联系博主-Q:782827013

  7. 比较git commit 两个版本之间次数

    #!/bin/bash f1="$1*" f2="$2*" echo "第一个版本:"$f1 echo "第二个版本:" ...

  8. 手动编译安装lamp之mysql

    转自马哥教育的讲课文档 二.安装mysql-5.5.28 1.准备数据存放的文件系统 新建一个逻辑卷,并将其挂载至特定目录即可.这里不再给出过程. 这里假设其逻辑卷的挂载目录为/mydata,而后需要 ...

  9. WCF实现进程间管道通信Demo

    一.代码结构: 二.数据实体类: using System; using System.Collections.Generic; using System.Linq; using System.Run ...

  10. 基于GeoServer切片地图服务的发布

    接着上一篇文章,如何将JPG格式的图片转化为带地理坐标的TIFF格式里提及的最近的一个项目,数据源是一张高分辨率的2.5维图片,现在已经成功转化成了带有地理坐标的TIFF格式.下面将介绍借助GeoSe ...