首页 > 语言 > JavaScript > 正文

angularjs 源码解析之scope

2024-05-06 14:52:48
字体:
来源:转载
供稿:网友

简介

在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();            
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选