售后服务当前位置:星鸿娱乐 > 售后服务 > >

angularjs 源码解析之scope

  

[angularjs,scope,angularjs,作用域]angularjs 源码解析之scope

  

简介  

  

在ng的生态中scope处于一个核心的地位,ng对外宣称的双向绑定的底层其实就是scope实现的,本章主要对scope的watch机制、继承性以及事件的实现作下分析。

  

  

监听  

  

1. $watch  

  

1.1 使用  

  

// $watch: function(watchExp, listener, objectEquality)  

  

var unwatch = $scope.$watch('aa', function () {}, isEqual);  
  

  

使用过angular的会经常这上面这样的代码,俗称“手动”添加监听,其他的一些都是通过插值或者directive自动地添加监听,但是原理上都一样。

  

  

1.2 源码分析  

  
  
  function(watchExp, listener, objectEquality) {  var scope = this,  // 将可能的字符串编译成fn  get = compileToFn(watchExp, 'watch'),  array = scope.$$watchers,  watcher = {  fn: listener,  last: initWatchVal,  // 上次值记录,方便下次比较  get: get,  exp: watchExp,  eq: !!objectEquality // 配置是引用比较还是值比较  };  lastDirtyWatch = null;  if (!isFunction(listener)) {  var listenFn = compileToFn(listener || noop, 'listener');  watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};  }  if (!array) {  array = scope.$$watchers = [];  }  // 之所以使用unshift不是push是因为在 $digest 中watchers循环是从后开始  // 为了使得新加入的watcher也能在当次循环中执行所以放到队列最前  array.unshift(watcher);  // 返回unwatchFn, 取消监听  return function deregisterWatch() {  arrayRemove(array, watcher);  lastDirtyWatch = null;  };  }  
  
  

从代码看 $watch 还是比较简单,主要就是将 watcher 保存到 $$watchers 数组中  

  

2. $digest  

  

当 scope 的值发生改变后,scope是不会自己去执行每个watcher的listenerFn,必须要有个通知,而发送这个通知的就是 $digest  

  

2.1 源码分析  

  

整个 $digest 的源码差不多100行,主体逻辑集中在【脏值检查循环】(dirty check loop) 中, 循环后也有些次要的代码,如 postDigestQueue 的处理等就不作详细分析了。

  

  

脏值检查循环,意思就是说只要还有一个 watcher 的值存在更新那么就要运行一轮检查,直到没有值更新为止,当然为了减少不必要的检查作了一些优化。

  

  

代码:  

  
  
  // 进入$digest循环打上标记,防止重复进入  beginPhase('$digest');  lastDirtyWatch = null;  // 脏值检查循环开始  do {  dirty = false;  current = target;  // asyncQueue 循环省略  traverseScopesLoop:  do {  if ((watchers = current.$$watchers)) {  length = watchers.length;  while (length--) {  try {  watch = watchers[length];  if (watch) {  // 作更新判断,是否有值更新,分解如下  // value = watch.get(current), last = watch.last  // value !== last 如果成立,则判断是否需要作值判断 watch.eq?equals(value, last)  // 如果不是值相等判断,则判断 NaN的情况,即 NaN !== NaN  if ((value = watch.get(current)) !== (last = watch.last) &&  !(watch.eq  ? equals(value, last)  : (typeof value === 'number' && typeof last === 'number'  && isNaN(value) && isNaN(last)))) {  dirty = true;  // 记录这个循环中哪个watch发生改变  lastDirtyWatch = watch;  // 缓存last值  watch.last = watch.eq ? copy(value, null) : value;  // 执行listenerFn(newValue, lastValue, scope)  // 如果第一次执行,那么 lastValue 也设置为newValue  watch.fn(value, ((last === initWatchVal) ? value : last), current);  // ... watchLog 省略  if (watch.get.$$unwatch) stableWatchesCandidates.push({watch: watch, array: watchers});  }  // 这边就是减少watcher的优化  // 如果上个循环最后一个更新的watch没有改变,即本轮也没有新的有更新的watch  // 那么说明整个watches已经稳定不会有更新,本轮循环就此结束,剩下的watch就不用检查了  else if (watch === lastDirtyWatch) {  dirty = false;  break traverseScopesLoop;  }  }  } catch (e) {  clearPhase();  $exceptionHandler(e);  }  }  }  // 这段有点绕,其实就是实现深度优先遍历  // A->[B->D,C->E]  // 执行顺序 A,B,D,C,E  // 每次优先获取第一个child,如果没有那么获取nextSibling兄弟,如果连兄弟都没了,那么后退到上一层并且判断该层是否有兄弟,没有的话继续上退,直到退到开始的scope,这时next==null,所以会退出scopes的循环  if (!(next = (current.$$childHead ||  (current !== target && current.$$nextSibling)))) {  while(current !== target && !(next = current.$$nextSibling)) {  current = current.$parent;  }  }  } while ((current = next));  // break traverseScopesLoop 直接到这边  // 判断是不是还处在脏值循环中,并且已经超过最大检查次数 ttl默认10  if((dirty || asyncQueue.length) && !(ttl--)) {  clearPhase();  throw $rootScopeMinErr('infdig',  '{0} $digest() iterations reached. Aborting!\n' +  'Watchers fired in the last 5 iterations: {1}',  TTL, toJson(watchLog));  }  } while (dirty || asyncQueue.length); // 循环结束  // 标记退出digest循环  clearPhase();  
  
  

上述代码中存在3层循环  

  

第一层判断 dirty,如果有脏值那么继续循环  

  

do {  

  

// ...  

  

} while (dirty)  
  

  

第二层判断 scope 是否遍历完毕,代码翻译了下,虽然还是绕但是能看懂  

  

do {  

  

// ....  

  

if (current.$$childHead) {  
  next = current.$$childHead;  
  } else if (current !== target && current.$$nextSibling) {  
  next = current.$$nextSibling;  
  }  
  while (!next && current !== target && !(next = current.$$nextSibling)) {  
  current = current.$parent;  
  }  
  }星鸿娱乐 while (current = next);
  
  

  

第三层循环scope的 watchers  

  

length = watchers.length;  
  while (length--) {  
  try {  
  watch = watchers[length];  
  
  // ... 省略
  

  

} catch (e) {  
  clearPhase();  
  $exceptionHandler(e);  
  }  
  }
  
  

  

3. $evalAsync  

  

3.1 源码分析  

  

$evalAsync用于延迟执行,源码如下:  

  
  
  function(expr) {  if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {  $browser.defer(function() {  if ($rootScope.$$asyncQueue.length) {  $rootScope.$digest();  }  });  }  this.$$asyncQueue.push({scope: this, expression: expr});  }  
  
  

通过判断是否已经有 dirty check 在运行,或者已经有人触发过$evalAsync  

  
  
  if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length)  $browser.defer 就是通过调用 setTimeout 来达到改变执行顺序  $browser.defer(function() {  //...

  });  

  
  

如果不是使用defer,那么  

  
  
  function (exp) {  queue.push({scope: this, expression: exp});  this.$digest();  }  scope.$evalAsync(fn1);  scope.$evalAsync(fn2);  // 这样的结果是  // $digest() > fn1 > $digest() > fn2  // 但是实际需要达到的效果:$digest() > fn1 > fn2  
  
  

上节 $digest 中省略了了async 的内容,位于第一层循环中  

  
  
  while(asyncQueue.length) {  try {  asyncTask = asyncQueue.shift();  asyncTask.scope.$eval(asyncTask.expression);  } catch (e) {  clearPhase();  $exceptionHandler(e);  }  lastDirtyWatch = null;  }  
  
  

简单易懂,弹出asyncTask进行执行。

  

  

不过这边有个细节,为什么这么设置呢?原因如下,假如在某次循环中执行到watchX时新加入1个asyncTask,此时会设置 lastDirtyWatch=watchX,恰好该task执行会导致watchX后续的一个watch执行出新值,如果没有下面的代码,那么下个循环到 lastDirtyWatch (watchX)时便跳出循环,并且此时dirty==false。

  

  

lastDirtyWatch = null;  
  

  

还有这边还有一个细节,为什么在第一层循环呢?因为具有继承关系的scope其 $$asyncQueue 是公用的,都是挂载在root上,故不需要在下一层的scope层中执行。

  

  

2. 继承性  

  

scope具有继承性,如 $parentScope, $childScope 两个scope,当调用 $childScope.fn 时如果 $childScope 中没有 fn 这个方法,那么就是去 $parentScope上查找该方法。

  

  

这样一层层往上查找直到找到需要的属性。这个特性是利用 javascirpt 的原型继承的特点实现。

  

  

源码:  

  
  
  function(isolate) {  var ChildScope,  child;  if (isolate) {  child = new Scope();  child.$root = this.$root;  // isolate 的 asyncQueue 及 postDigestQueue 也都是公用root的,其他独立  child.$$asyncQueue = this.$$asyncQueue;  child.$$postDigestQueue = this.$$postDigestQueue;  } else {  if (!this.$$childScopeClass) {  this.$$childScopeClass = function() {  // 这里可以看出哪些属性是隔离独有的,如$$watchers, 这样就独立监听了,  this.$$watchers = this.$$nextSibling =  this.$$childHead = this.$$childTail = null;  this.$$listeners = {};  this.$$listenerCount = {};  this.$id = nextUid();  this.$$childScopeClass = null;  };  this.$$childScopeClass.prototype = this;  }  child = new this.$$childScopeClass();  }  // 设置各种父子,兄弟关系,很乱!

  child['this'] = child;  child.$parent = this;  child.$$prevSibling = this.$$childTail;  if (this.$$childHead) {  this.$$childTail.$$nextSibling = child;  this.$$childTail = child;  } else {  this.$$childHead = this.$$childTail = child;  }  return child;  }  

  
  

代码还算清楚,主要的细节是哪些属性需要独立,哪些需要基础下来。

  

  

最重要的代码:  

  

this.$$childScopeClass.prototype = this;  
  

  

就这样实现了继承。

  

  

3. 事件机制  

  

3.1 $on  

  
  
  function(name, listener) {  var namedListeners = this.$$listeners[name];  if (!namedListeners) {  this.$$listeners[name] = namedListeners = [];  }  namedListeners.push(listener);  var current = this;  do {  if (!current.$$listenerCount[name]) {  current.$$listenerCount[name] = 0;  }  current.$$listenerCount[name]++;  } while ((current = current.$parent));  var self = this;  return function() {  namedListeners[indexOf(namedListeners, listener)] = null;  decrementListenerCount(self, 1, name);  };  }  
  
  

跟 $wathc 类似,也是存放到数组 -- namedListeners。

  

  

还有不一样的地方就是该scope和所有parent都保存了一个事件的统计数,广播事件时有用,后续分析。

  

  
  
  var current = this;  do {  if (!current.$$listenerCount[name]) {  current.$$listenerCount[name] = 0;  }  current.$$listenerCount[name]++;  } while ((current = current.$parent));  
  
  

3.2 $emit  

  

$emit 是向上广播事件。源码:  

  
  
  function(name, args) {  var empty = [],  namedListeners,  scope = this,  stopPropagation = false,  event = {  name: name,  targetScope: scope,  stopPropagation: function() {stopPropagation = true;},  preventDefault: function() {  event.defaultPrevented = true;  },  defaultPrevented: false  },  listenerArgs = concat([event], arguments, 1),  i, length;  do {  namedListeners = scope.$$listeners[name] || empty;  event.currentScope = scope;  for (i=0, length=namedListeners.length; i  
  

3.3 $broadcast  

  

$broadcast 是向内传播,即向child传播,源码:  

  
  
  function(name, args) {  var target = this,  current = target,  next = target,  event = {  name: name,  targetScope: target,  preventDefault: function() {  event.defaultPrevented = true;  },  defaultPrevented: false  },  listenerArgs = concat([event], arguments, 1),  listeners, i, length;  while ((current = next)) {  event.currentScope = current;  listeners = current.$$listeners[name] || [];  for (i=0, length = listeners.length; i  
  

其他逻辑比较简单,就是在深度遍历的那段代码比较绕,其实跟digest中的一样,就是多了在路径上判断是否有监听,current.$$listenerCount[name],从上面$on的代码可知,只要路径上存在child有监听,那么该路径头也是有数字的,相反如果没有说明该路径上所有child都没有监听事件。

  

  
  
  if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||  (current !== target && current.$$nextSibling)))) {  while(current !== target && !(next = current.$$nextSibling)) {  current = current.$parent;  }  }  
  
  

传播路径:  

  

Root>[A>[a1,a2], B>[b1,b2>[c1,c2],b3]]  

  

Root > A > a1 > a2 > B > b1 > b2 > c1 > c2 > b3  
  

  

4. $watchCollection  

  

4.1 使用示例  

  
  
  $scope.names = ['igor', 'matias', 'misko', 'james'];  $scope.dataCount = 4;  $scope.$watchCollection('names', function(newNames, oldNames) {  $scope.dataCount = newNames.length;  });  expect($scope.dataCount).toEqual(4);  $scope.$digest();  expect($scope.dataCount).toEqual(4);  $scope.names.pop();  $scope.$digest();  expect($scope.dataCount).toEqual(3);  
  
  

4.2 源码分析  

  
  
  function(obj, listener) {  $watchCollectionInterceptor.$stateful = true;  var self = this;  var newValue;  var oldValue;  var veryOldValue;  var trackVeryOldValue = (listener.length > 1);  var changeDetected = 0;  var changeDetector = $parse(obj, $watchCollectionInterceptor);  var internalArray = [];  var internalObject = {};  var initRun = true;  var oldLength = 0;  // 根据返回的changeDetected判断是否变化  function $watchCollectionInterceptor(_value) {  // ...

  return changeDetected;  }  // 通过此方法调用真正的listener,作为代理  function $watchCollectionAction() {  }  return this.$watch(changeDetector, $watchCollectionAction);  }  

  
  

主脉络就是上面截取的部分代码,下面主要分析 $watchCollectionInterceptor 和 $watchCollectionAction  

  

4.3 $watchCollectionInterceptor  

  
  
  function $watchCollectionInterceptor(_value) {  newValue = _value;  var newLength, key, bothNaN, newItem, oldItem;  if (isUndefined(newValue)) return;  if (!isObject(newValue)) {  if (oldValue !== newValue) {  oldValue = newValue;  changeDetected++;  }  } else if (isArrayLike(newValue)) {  if (oldValue !== internalArray) {  oldValue = internalArray;  oldLength = oldValue.length = 0;  changeDetected++;  }  newLength = newValue.length;  if (oldLength !== newLength) {  changeDetected++;  oldValue.length = oldLength = newLength;  }  for (var i = 0; i < newLength; i++) {  oldItem = oldValue[i];  newItem = newValue[i];  bothNaN = (oldItem !== oldItem) && (newItem !== newItem);  if (!bothNaN && (oldItem !== newItem)) {  changeDetected++;  oldValue[i] = newItem;  }  }  } else {  if (oldValue !== internalObject) {  oldValue = internalObject = {};  oldLength = 0;  changeDetected++;  }  newLength = 0;  for (key in newValue) {  if (hasOwnProperty.call(newValue, key)) {  newLength++;  newItem = newValue[key];  oldItem = oldValue[key];  if (key in oldValue) {  bothNaN = (oldItem !== oldItem) && (newItem !== newItem);  if (!bothNaN && (oldItem !== newItem)) {  changeDetected++;  oldValue[key] = newItem;  }  } else {  oldLength++;  oldValue[key] = newItem;  changeDetected++;  }  }  }  if (oldLength > newLength) {  changeDetected++;  for (key in oldValue) {  if (!hasOwnProperty.call(newValue, key)) {  oldLength--;  delete oldValue[key];  }  }  }  }  return changeDetected;  }  
  
  

1). 当值为undefined时直接返回。

  

  

2). 当值为普通基本类型时 直接判断是否相等。

  

  

3). 当值为类数组 (即存在 length 属性,并且 value[i] 也成立称为类数组),先没有初始化先初始化oldValue  

  
  
  if (oldValue !== internalArray) {  oldValue = internalArray;  oldLength = oldValue.length = 0;  changeDetected++;  }  
  
  

然后比较数组长度,不等的话记为已变化 changeDetected++  

  
  
  if (oldLength !== newLength) {  changeDetected++;  oldValue.length = oldLength = newLength;  }  
  
  

再进行逐个比较  

  
  
  for (var i = 0; i < newLength; i++) {  oldItem = oldValue[i];  newItem = newValue[i];  bothNaN = (oldItem !== oldItem) && (newItem !== newItem);  if (!bothNaN && (oldItem !== newItem)) {  changeDetected++;  oldValue[i] = newItem;  }  }  
  
  

4). 当值为object时,类似上面进行初始化处理  

  
  
  if (oldValue !== internalObject) {  oldValue = internalObject = {};  oldLength = 0;  changeDetected++;  }  
  
  

接下来的处理比较有技巧,但凡发现 newValue 多的新字段,就在oldLength 加1,这样 oldLength 只加不减,很容易发现 newValue 中是否有新字段出现,最后把 oldValue中多出来的字段也就是 newValue 中删除的字段给移除就结束了。

  

  
  
  newLength = 0;  for (key in newValue) {  if (hasOwnProperty.call(newValue, key)) {  newLength++;  newItem = newValue[key];  oldItem = oldValue[key];  if (key in oldValue) {  bothNaN = (oldItem !== oldItem) && (newItem !== newItem);  if (!bothNaN && (oldItem !== newItem)) {  changeDetected++;  oldValue[key] = newItem;  }  } else {  oldLength++;  oldValue[key] = newItem;  changeDetected++;  }  }  }  if (oldLength > newLength) {  changeDetected++;  for (key in oldValue) {  if (!hasOwnProperty.call(newValue, key)) {  oldLength--;  delete oldValue[key];  }  }  }  
  
  

4.4 $watchCollectionAction  

  
  
  function $watchCollectionAction() {  if (initRun) {  initRun = false;  listener(newValue, newValue, self);  } else {  listener(newValue, veryOldValue, self);  }  // trackVeryOldValue = (listener.length > 1) 查看listener方法是否需要oldValue  // 如果需要就进行复制  if (trackVeryOldValue) {  if (!isObject(newValue)) {  veryOldValue = newValue;  } else if (isArrayLike(newValue)) {  veryOldValue = new Array(newValue.length);  for (var i = 0; i < newValue.length; i++) {  veryOldValue[i] = newValue[i];  }  } else {  veryOldValue = {};  for (var key in newValue) {  if (hasOwnProperty.call(newValue, key)) {  veryOldValue[key] = newValue[key];  }  }  }  }  }  
  
  

代码还是比较简单,就是调用 listenerFn,初次调用时 oldValue == newValue,为了效率和内存判断了下 listener是否需要oldValue参数  

  

5. $eval & $apply  

  
  
  $eval: function(expr, locals) {  return $parse(expr)(this, locals);  },  $apply: function(expr) {  try {  beginPhase('$apply');  return this.$eval(expr);  } catch (e) {  $exceptionHandler(e);  } finally {  clearPhase();  try {  $rootScope.$digest();  } catch (e) {  $exceptionHandler(e);  throw e;  }  }  }  
  
  

$apply 最后调用 $rootScope.$digest(),所以很多书上建议使用 $digest() ,而不是调用 $apply(),效率要高点。

  

  

主要逻辑都在$parse 属于语法解析功能,后续单独分析。

  

(责任编辑:admin)

上一篇:基于JavaScript 类的使用详解

下一篇:没有了

推荐内容

客户服务热线

400 888 8932

在线客服