RootScopeProvider简介

RootScopeProvider是angularjs里面比较活跃的一个provider。它主要用来生成实例rootScope,它代表angularjs应用的根作用域。我们可以把它看成MVVM模式中的VM。

源代码如下:

  1. function $RootScopeProvider(){
  2. var TTL = 10;
  3. var $rootScopeMinErr = minErr('$rootScope');
  4. var lastDirtyWatch = null;
  5.  
  6. this.digestTtl = function(value) {
  7. if (arguments.length) {
  8. TTL = value;
  9. }
  10. return TTL;
  11. };
  12. this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
  13. function( $injector, $exceptionHandler, $parse, $browser) {
  14. .......
  15. }];
  16. }

在$RootScopeProvider构造函数中,有一个$get属性,它的值是一个数组,数组的最后的一项是一个函数function( $injector,   $exceptionHandler,   $parse,   $browser) ,所有的代码都在这个方法中。angularJS内置的provider都有一个$get属性,此属性值主要是定义实例化provider对象的函数体,实例化时,会通过instanceinjector.invoke来调用。在$get属性上面还有一个digestTtl属性,这个属性是用来修改angularJS默认的dirty check次数的,angularJS默认是10次。接下来,我们来看下$get属性值中的实例化

$RootScopeProvider的函数体。

  1.          function Scope() {
  2. this.$id = nextUid();
  3. this.$$phase = this.$parent = this.$$watchers =
  4. this.$$nextSibling = this.$$prevSibling =
  5. this.$$childHead = this.$$childTail = null;
  6. this['this'] = this.$root = this;
  7. this.$$destroyed = false;
  8. this.$$asyncQueue = [];
  9. this.$$postDigestQueue = [];
  10. this.$$listeners = {};
  11. this.$$listenerCount = {};
  12. this.$$isolateBindings = {};
  13. }
  14. Scope.prototype = {
  15. constructor: Scope,
  16. $new: function(isolate) {
  17. .....
  18. },
  19. $watch: function(watchExp, listener, objectEquality) {
  20.   ......
  21. },
  22. $watchCollection: function(obj, listener) {
  23. var self = this;
  24. var oldValue;
  25. var newValue;
  26. var changeDetected = 0;
  27. var objGetter = $parse(obj);
  28. var internalArray = [];
  29. var internalObject = {};
  30. var oldLength = 0;
  31. function $watchCollectionWatch() {
  32. newValue = objGetter(self);
  33. var newLength, key;
  34. if (!isObject(newValue)) {
  35. if (oldValue !== newValue) {
  36. oldValue = newValue;
  37. changeDetected++;
  38. }
  39. } else if (isArrayLike(newValue)) {
  40. if (oldValue !== internalArray) {
  41. oldValue = internalArray;
  42. oldLength = oldValue.length = 0;
  43. changeDetected++;
  44. }
  45. newLength = newValue.length;
  46. if (oldLength !== newLength) {
  47. changeDetected++;
  48. oldValue.length = oldLength = newLength;
  49. }
  50. for (var i = 0; i < newLength; i++) {
  51. if (oldValue[i] !== newValue[i]) {
  52. changeDetected++;
  53. oldValue[i] = newValue[i];
  54. }
  55. }
  56. } else {
  57. if (oldValue !== internalObject) {
  58. oldValue = internalObject = {};
  59. oldLength = 0;
  60. changeDetected++;
  61. }
  62. newLength = 0;
  63. for (key in newValue) {
  64. if (newValue.hasOwnProperty(key)) {
  65. newLength++;
  66. if (oldValue.hasOwnProperty(key)) {
  67. if (oldValue[key] !== newValue[key]) {
  68. changeDetected++;
  69. oldValue[key] = newValue[key];
  70. }
  71. } else {
  72. oldLength++;
  73. oldValue[key] = newValue[key];
  74. changeDetected++;
  75. }
  76. }
  77. }
  78. if (oldLength > newLength) {
  79. changeDetected++;
  80. for(key in oldValue) {
  81. if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) {
  82. oldLength--;
  83. delete oldValue[key];
  84. }
  85. }
  86. }
  87. }
  88. return changeDetected;
  89. }
  90. function $watchCollectionAction() {
  91. listener(newValue, oldValue, self);
  92. }
  93. return this.$watch($watchCollectionWatch, $watchCollectionAction);
  94. },
  95. $digest: function() {
  96. ........
  97. },
  98. $destroy: function() {
  99. ......
  100. },
  101. $eval: function(expr, locals) {
  102. .....
  103. },
  104. $evalAsync: function(expr) {
  105. .......
  106. },
  107. $$postDigest : function(fn) {
  108. ........
  109. },
  110. $apply: function(expr) {
  111. ......
  112. },
  113. $on: function(name, listener) {
  114. ......
  115. },
  116. $emit: function(name, args) {
  117. .......
  118. },
  119. $broadcast: function(name, args) {
  120.   ......
  121. }
  122. };
  123. var $rootScope = new Scope();
  124. return $rootScope;
  125. function beginPhase(phase) {
  126. if ($rootScope.$$phase) {
  127. throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
  128. }
  129. $rootScope.$$phase = phase;
  130. }
  131. function clearPhase() {
  132. $rootScope.$$phase = null;
  133. }
  134. function compileToFn(exp, name) {
  135. var fn = $parse(exp);
  136. assertArgFn(fn, name);
  137. return fn;
  138. }
  139. function decrementListenerCount(current, count, name) {
  140. do {
  141. current.$$listenerCount[name] -= count;
  142. if (current.$$listenerCount[name] === 0) {
  143. delete current.$$listenerCount[name];
  144. }
  145. } while ((current = current.$parent));
  146. }
  147. function initWatchVal() {}

从代码中,可以看到。第一步,定义了一个构造函数function Scope(){},第二步,定义了Scope的原型Scope.prototype = {},第三步,new出来了一个Scope,赋值给了$rootScope,并return这个Scope实例对象。

通过第三步大家可以看出,angularJS默认会创建根作用域$rootScope,并作为$rootScopeprovider的实例对象返回。

第一步,初始化了Scope对象的一些属性值。

第二步,定义了一些方法。我们来详细讲解这些方法的作用:

  1. $new: function(isolate) {
  2. var ChildScope,
  3. child;
  4.  
  5. if (isolate) {
  6. child = new Scope();
  7. child.$root = this.$root;
  8. // ensure that there is just one async queue per $rootScope and its children
  9. child.$$asyncQueue = this.$$asyncQueue;
  10. child.$$postDigestQueue = this.$$postDigestQueue;
  11. } else {
  12. ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges
  13. // the name it does not become random set of chars. This will then show up as class
  14. // name in the web inspector.
  15. ChildScope.prototype = this;
  16. child = new ChildScope();
  17. child.$id = nextUid();
  18. }
  19. child['this'] = child;
  20. child.$$listeners = {};
  21. child.$$listenerCount = {};
  22. child.$parent = this;
  23. child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
  24. child.$$prevSibling = this.$$childTail;
  25. if (this.$$childHead) {
  26. this.$$childTail.$$nextSibling = child;
  27. this.$$childTail = child;
  28. } else {
  29. this.$$childHead = this.$$childTail = child;
  30. }
  31. return child;
  32. },

此方法的作用是用来创建子作用域。

  • isolate变量的值决定是执行if语句的代码还是else语句的代码,如果isolate的值是true,那么就会创建一个独立的作用域。这个在我们创建指令,并且在创建指令的回调方法中有scope属性的情况下,会出现这种情况,当然还有其他别的特殊情况下也会这样。假如是独立作用域的话,会多一个$root属性,它的值默认指向rootscope。

  • 如果isolate的值为false,则会定义一个空的构造函数ChildScope ,并且把此构造函数的prototype指向当前scope的实例对象,然后,new一个ChildScope 对象。

  • 然后为生成的子作用域设置$parent属性为当前作用域,并且设置子作用域的一些默认的属性值,然后设置当前作用域的一些属性值为生成的子作用域。最后,返回这个新生成的子作用域child。

接下来,我们再来说说$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. lastDirtyWatch = null;
  13. if (!isFunction(listener)) {
  14. var listenFn = compileToFn(listener || noop, 'listener');
  15. watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
  16. }
  17. if (typeof watchExp == 'string' && get.constant) {
  18. var originalFn = watcher.fn;
  19. watcher.fn = function(newVal, oldVal, scope) {
  20. originalFn.call(this, newVal, oldVal, scope);
  21. arrayRemove(array, watcher);
  22. };
  23. }
  24. if (!array) {
  25. array = scope.$$watchers = [];
  26. }
  27. array.unshift(watcher);
  28. return function() {
  29. arrayRemove(array, watcher);
  30. lastDirtyWatch = null;
  31. };
  32. }

$watch方法有三个参数,第一个是监听表达式,可以是字符串也可以是方法,第二个是监听方法,第三个参数代表是否深度监听。

方法里面首先初始化一个get局部变量,初始化get的compileToFn函数其实是调用$parse实例对象来解析监听参数的,它会返回一个方法赋给get属性,这个方法会在dirty check里用到,用来获取监听表达式的值。$parse实例对象是$parseprovider实例化生成的。

接下来,初始化了一个watcher对象,此对象用来保存一些监听相关的信息:

  • fn: 代表监听方法,当监听表达式有变化时,也就说值改变时会执行此方法
  • last: 保存最后一次发生变化的监听表达式的值
  • get: 保存一个监听表达式对应的方法,目的是用来获取表达式的值然后用来进行新旧对比,看是否有变化
  • exp: 保存一个原始的监听表达式
  • eq: 保存$watch方法的第三个参数,表示是否进行深度比较

然后会检查传递进来的监听方法是否为方法,如果不是,则通过parse方法解析生成一个方法listenFn ,然后通过包装这个listenFn方法生成一个方法watcher.fn

,此方法就是被称为监听方法,并且此方法体的内容就是执行刚才生成的方法listenFn,并默认传递当前作用域作为参数。

接着会检查监听表达式是否为字符串,如果是并且监听表达式的constant为true,就进入if语句,这代表监听表达式这个字符串是一个常量。那么,angular在处理这种监听的时候,执行完一次监听方法之后就会删除这个$watch。

最后,往当前作用域里的$$watchers数组中添加$watch对象,并返回一个方法。注意这里的返回值,利用JS的闭包保留了当前的watcher变量,这个返回值方法是用来删除监听用的。

接下来,我们来说说$digest方法

digest方法是dirty check的核心,它里面的代码是先执行$$asyncQueue队列中保存的表达式,然后开启一个traverseScopesLoop循环,来循环遍历$$watchers,如果watch与上一次的值不相同,也就是被改变了,就执行watch里的监听方法。假如ttl超过了angular默认设置的值,则dirth check结束。最后执行$$postDigestQueue队列里的表达式。

  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. beginPhase('$digest');
  12. lastDirtyWatch = null;
  13. do {
  14. dirty = false;
  15. current = target;
  16. while(asyncQueue.length) {
  17. try {
  18. asyncTask = asyncQueue.shift();
  19. asyncTask.scope.$eval(asyncTask.expression);
  20. } catch (e) {
  21. clearPhase();
  22. $exceptionHandler(e);
  23. }
  24. lastDirtyWatch = null;
  25. }
  26. traverseScopesLoop:
  27. do {
  28. if ((watchers = current.$$watchers)) {
  29. length = watchers.length;
  30. while (length--) {
  31. try {
  32. watch = watchers[length];
  33. if (watch) {
  34. if ((value = watch.get(current)) !== (last = watch.last) &&
  35. !(watch.eq
  36. ? equals(value, last)
  37. : (typeof value == 'number' && typeof last == 'number'
  38. && isNaN(value) && isNaN(last)))) {
  39. dirty = true;
  40. lastDirtyWatch = watch;
  41. watch.last = watch.eq ? copy(value) : value;
  42. watch.fn(value, ((last === initWatchVal) ? value : last), current);
  43. if (ttl < 5) {
  44. logIdx = 4 - ttl;
  45. if (!watchLog[logIdx]) watchLog[logIdx] = [];
  46. logMsg = (isFunction(watch.exp))
  47. ? 'fn: ' + (watch.exp.name || watch.exp.toString())
  48. : watch.exp;
  49. logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
  50. watchLog[logIdx].push(logMsg);
  51. }
  52. } else if (watch === lastDirtyWatch) {
  53. dirty = false;
  54. break traverseScopesLoop;
  55. }
  56. }
  57. } catch (e) {
  58. clearPhase();
  59. $exceptionHandler(e);
  60. }
  61. }
  62. }
  63. if (!(next = (current.$$childHead ||
  64. (current !== target && current.$$nextSibling)))) {
  65. while(current !== target && !(next = current.$$nextSibling)) {
  66. current = current.$parent;
  67. }
  68. }
  69. } while ((current = next));
  70. if((dirty || asyncQueue.length) && !(ttl--)) {
  71. clearPhase();
  72. throw $rootScopeMinErr('infdig',
  73. '{0} $digest() iterations reached. Aborting!\n' +
  74. 'Watchers fired in the last 5 iterations: {1}',
  75. TTL, toJson(watchLog));
  76. }
  77. } while (dirty || asyncQueue.length);
  78. clearPhase();
  79. while(postDigestQueue.length) {
  80. try {
  81. postDigestQueue.shift()();
  82. } catch (e) {
  83. $exceptionHandler(e);
  84. }
  85. }
  86. },

通过上面的代码,可以看出,里面的核心就是两个循环,外循环保证所有的model都能检测到(do{.....} while (dirty || asyncQueue.length);),内循环则是真正的检测每个watch(traverseScopesLoop: do{......}while((current = next));)。

watch.get方法的作用是计算监听表达式的值,然后用当前的值去跟旧值进行对比,假如不相等,也就是改变了,就执行监听方法。

注意这里的watch.eq,它的作用是是否进行深度检查,equals方法是angular里的方法,用来深度对比两个对象。这里的不相等有一个例外,那就是NaN === NaN,这个永远都是false,所以这里加了判断。

比较完之后,把新值传给watch.last,然后执行watch.fn,也就是监听方法,传递三个参数给它,分别是:最新计算的值,上次计算的值(如果是第一次,则传递新值),最后一个参数是当前作用域的实例对象。

内循环里面有一个设置外循环的条件值,那就是dirty = true,也就是说只要内循环执行了一次watch,则外循环还要继续执行,这是为了保证所有的model都能监测一次,虽然这个有点浪费性能,不过超过ttl设置的值,dirty check就会强制关闭,并抛出异常。

这里的watchLog日志对象是在内循环里,当ttl低于5的时候开始记录的。

当检查完一个作用域内的所有watch之后,则开始深度遍历当前作用域的子级作用域或者父级作用域,虽然这有些影响性能。它就是不断的查找当前作用域的子级,没有子级,则开始查找兄弟节点,最后查找它的父级,是一个深度遍历查找。只要next有值,则内循环一直执行。

不过内循环也有跳出的情况,那就是当前的watch跟最后一次检查的watch相等时,就退出内循环。

注意:这个内循环是一个label(标签)语句,这个可以在循环中执行跳出操作,就像上面的break traverseScopesLoop。

正常执行完两个循环之后,清除当前的阶段标识clearPhase();,然后开始执行postDigestQueue队列里的表达式。

接下来我们来说下$destroy方法

  1. $destroy: function() {
  2. if (this.$$destroyed) return;
  3. var parent = this.$parent;
  4. this.$broadcast('$destroy');
  5. this.$$destroyed = true;
  6. if (this === $rootScope) return;
  7. forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
  8. if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
  9. if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
  10. if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
  11. if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
  12. this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
  13. this.$$childTail = null;
  14. },

这个方法是用来销毁当前作用域的,原理主要是清空当前作用域内的一些属性,以免执行digest,$emit,$broadcast时会关联到。

里面的代码比较简单,先是通过foreach语句来遍历$$listenerCount属性,清空$$listeners,$$watchers,$$asyncQueue,$$postDigestQueue里面的值,然后再设置$parent,$$nextSibling,$$prevSibling,$$childHead,$$childTail为null。

接下来我们来说下$eval方法

此方法可以直接在angular程序里执行一个字符串表达式:

  1. $eval: function(expr, locals) {
  2. return $parse(expr)(this, locals);
  3. },

它的源码其实就是通过parse方法把你传进去的字符串表达式解析成一个执行表达式的方法,然后传递当前作用域以及额外的参数执行此方法。

接下来我们来说下$evalAsync方法

evalAsync方法的作用就是延迟执行表达式,并且执行完成后,不管是否异常,都触发dirty check。

  1. $evalAsync: function(expr) {
  2. if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
  3. $browser.defer(function() {
  4. if ($rootScope.$$asyncQueue.length) {
  5. $rootScope.$digest();
  6. }
  7. });
  8. }
  9. this.$$asyncQueue.push({scope: this, expression: expr});
  10. },

从上面的源码中可以看到,当前作用域内部有一个$$asyncQueue异步队列,它保存着所有需要延迟执行的表达式,此处的表达式可以是字符串也可以是方法,因为这个表达式最终会调用$eval方法。注意这里调用了$browser服务的defer方法,此方法是调用setTimeout来实现的,它的作用是延迟执行方法。

接下来我们来说下$postDigest方法

  1. $$postDigest : function(fn) {
  2. this.$$postDigestQueue.push(fn);
  3. },

这个方法跟evalAsync不同的是,它不会主动触发digest方法,只是往postDigestQueue队列中添加执行表达式。它会在digest方法体内最后被执行,相当于在触发dirty check之后,我们可以执行别的一些逻辑。

接下来,我们来说下$apply方法

  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. },

如果不在angularjs的上下文中执行js,但是想操作angular中的值,则必须使用此方法了。比如:在原生的DOM事件中执行想改变angular中某些model的值,这个时候就要使用$apply方法了。

代码中,首先让当前阶段标识变为$apply,然后执行$eval方法, 这个方法上面有讲到,最后执行$digest方法,使angular中的model改变。

接下来,我们来说说scope中的event模块,它提供$on,$emit,$broadcast三个方法:

$on方法

  1. $on: function(name, listener) {
  2. var namedListeners = this.$$listeners[name];
  3. if (!namedListeners) {
  4. this.$$listeners[name] = namedListeners = [];
  5. }
  6. namedListeners.push(listener);
  7. var current = this;
  8. do {
  9. if (!current.$$listenerCount[name]) {
  10. current.$$listenerCount[name] = 0;
  11. }
  12. current.$$listenerCount[name]++;
  13. } while ((current = current.$parent));
  14. var self = this;
  15. return function() {
  16. namedListeners[indexOf(namedListeners, listener)] = null;
  17. decrementListenerCount(self, 1, name);
  18. };
  19. },

这个方法是用来绑定事件的,里面用到了两个实例变量$$listeners, $$listenerCount,分别用来保存事件,以及事件数量计数。

分析上面的代码,可以看出每当绑定一个事件的时候,都会向$$listeners对象中添加name的属性,属性值就是事件回调函数。注意这里有个事件计数器,只要有父级,则也给父级的$$listenerCount添加name的属性,并且把name的值+1。最后这个方法返回一个取消事件的方法,在方法中会先设置$$listeners中name的属性值为null,然后调用decrementListenerCount使该事件的计数器-1。

$emit方法

  1. $emit: function(name, args) {
  2. var empty = [],
  3. namedListeners,
  4. scope = this,
  5. stopPropagation = false,
  6. event = {
  7. name: name,
  8. targetScope: scope,
  9. stopPropagation: function() {stopPropagation = true;},
  10. preventDefault: function() {
  11. event.defaultPrevented = true;
  12. },
  13. defaultPrevented: false
  14. },
  15. listenerArgs = concat([event], arguments, 1),
  16. i, length;
  17. do {
  18. namedListeners = scope.$$listeners[name] || empty;
  19. event.currentScope = scope;
  20. for (i=0, length=namedListeners.length; i<length; i++) {
  21. if (!namedListeners[i]) {
  22. namedListeners.splice(i, 1);
  23. i--;
  24. length--;
  25. continue;
  26. }
  27. try {
  28. namedListeners[i].apply(null, listenerArgs);
  29. } catch (e) {
  30. $exceptionHandler(e);
  31. }
  32. }
  33. if (stopPropagation) return event;
  34. scope = scope.$parent;
  35. } while (scope);
  36. return event;
  37. },

这个方法是用来触发$on绑定的事件的。原理就是循环$$listeners,检查它里面是否有值,有的话,则执行。然后依次往上检查父级,这个方法有点类似浏览器的事件冒泡机制。

上面的代码比较简单,首先定义一个事件对象的变量event,然后开启一个循环,只要scope中有值,则一直执行。这个方法的事件链是向上传递的,不过如果在事件回调函数中执行stopPropagation方法,则会停止向上传递事件。

$broadcast方法

  1. $broadcast: function(name, args) {
  2. var target = this,
  3. current = target,
  4. next = target,
  5. event = {
  6. name: name,
  7. targetScope: target,
  8. preventDefault: function() {
  9. event.defaultPrevented = true;
  10. },
  11. defaultPrevented: false
  12. },
  13. listenerArgs = concat([event], arguments, 1),
  14. listeners, i, length;
  15. while ((current = next)) {
  16. event.currentScope = current;
  17. listeners = current.$$listeners[name] || [];
  18. for (i=0, length = listeners.length; i<length; i++) {
  19. if (!listeners[i]) {
  20. listeners.splice(i, 1);
  21. i--;
  22. length--;
  23. continue;
  24. }
  25. try {
  26. listeners[i].apply(null, listenerArgs);
  27. } catch(e) {
  28. $exceptionHandler(e);
  29. }
  30. }
  31. if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
  32. (current !== target && current.$$nextSibling)))) {
  33. while(current !== target && !(next = current.$$nextSibling)) {
  34. current = current.$parent;
  35. }
  36. }
  37. }
  38. return event;
  39. }

这个是$emit的升级版,广播事件。它既能向上传递事件,也能向下传递事件,还能平级传递事件。核心原理就是利用深度遍历当前作用域。

代码跟$emit差不多,不同的是,这个方法是不断的取next值,而next的值则是通过深度遍历它的子级节点,兄弟节点,父级节点,并依次查找可用的名为name的事件回调函数。$broadcast方法不能常用,因为性能不是很理想。

angular是通过观察者模式,把一个model绑定到多个view中的。

如何知道model发生了变化,其实就是通过上面代码中的$watch和$digest(我们称这两个操作为脏值检测)。

如果model是深层次嵌套的结构,我们是通过对象的深比较,来知道model的某个属性是不是变化了。

上面代码中的ttl是什么意思呢?假设a和b两个方法互相watch对方,这时,a变化了,b就会变化,b变化了,a就会变化,形成一个死循环,angular内部就是通过ttl(默认为10)的值来判定,如果一个对象的值发生10次检测的过程还在变化,那么angular会强制停止,并抛出一个异常。

angular在双向绑定的时候,是如何支持表达式的呢?其实就是通过angular自带的编译器$parser对表达式进行解析做到的。

加油!

AngularJS源码解析3:RootScope的创建过程的更多相关文章

  1. AngularJS源码解析1:angular自启动过程

    angularJS加载进来后,会有一个立即执行函数调用,在源代码的最下面是angular初始化的地方.代码展示: bindJQuery(); publishExternalAPI(angular); ...

  2. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  3. Flink 源码解析 —— JobManager 处理 SubmitJob 的过程

    JobManager 处理 SubmitJob https://t.zsxq.com/3JQJMzZ 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1 ...

  4. Flink 源码解析 —— TaskManager 处理 SubmitJob 的过程

    TaskManager 处理 SubmitJob 的过程 https://t.zsxq.com/eu7mQZj 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink ...

  5. Netty 源码解析(九): connect 过程和 bind 过程分析

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  6. AngularJS源码解析4:Parse解析器的详解

    $ParseProvider简介 此服务提供者也是angularjs中用的比较多的,下面我们来详细的说下这个provider. function $ParseProvider() { var cach ...

  7. QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)

    前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...

  8. spring源码系列8:AOP源码解析之代理的创建

    回顾 首先回顾: JDK动态代理与CGLIB动态代理 Spring中的InstantiationAwareBeanPostProcessor和BeanPostProcessor的区别 我们得知 JDK ...

  9. springIOC源码解析之Bean的创建

    上一篇讲到了beanFactory的配置文件的解析和beanFactory的创建,都集中到了obtainFreshBeanFactory();这一句代码里了,本篇主要讲bean的创建过程 public ...

随机推荐

  1. ansible 使用

    批量添加ssh免密 ansible mhc -m authorized_key -a "user=root key='{{ lookup('file','/root/.ssh/id_dsa. ...

  2. Android APK反编译详解(转)

    转自:http://blog.csdn.net/ithomer/article/details/6727581 这段时间在学Android应用开发,在想既然是用Java开发的应该很好反编译从而得到源代 ...

  3. Spring MVC的handlermapping之请求分发如何找到正确的Handler(BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping)

    本文讲的是Spring MVC如何找到正确的handler, 前面请求具体怎么进入到下面的方法,不再细说. 大概就是Spring mvc通过servlet拦截请求,实现doService方法,然后进入 ...

  4. Devexpress VCL Build v2013 vol 13.2.3 发布

    继续修修补补,大过年的,就不吐槽了. What's New in 13.2.3 (VCL Product Line)   New Major Features in 13.2 What's New i ...

  5. JScript 对象 <|> JSON

    <script type="text/javascript"> function test(){ var array = [{"id":1},{&q ...

  6. Git config 配置文件

    一.Git已经在你的系统中了,你会做一些事情来客户化你的Git环境.你只需要做这些设置一次:即使你升级了,他们也会绑定到你的环境中.你也可以在任何时刻通过运行命令来重新更改这些设置. Git有一个工具 ...

  7. Java之RandomAccessFile小结

    今天跟大家分享一下javase中的关于I/O的操作: 有时我们需要在文件的末尾追加一些内容,在这时用RandomAccessFile就很好. 这个类有两个构造方法: RandomAccessFile( ...

  8. 企业搜索引擎开发之连接器connector(二十七)

    ChangeQueue类实现ChangeSource接口,声明了拉取下一条Change对象的方法 * A source of {@link Change} objects. * * @since 2. ...

  9. 凭借对KMP算法的了解,用java实现了一下,结果和java自带的字符串indexOf比,性能差了十倍。。。

    public class KMP { private char[] source = {'a','b','c','b','c','a','b','a','b','d','d','e','f','g', ...

  10. [leetcode] 5. Minimum Depth of Binary Tree

    二叉树基本功练习题,题目如下: Given a binary tree, find its minimum depth. The minimum depth is the number of node ...