Angular 1.2 原理分析

$digest脏检查,先看下源码

//位置:rootScope.js
 $digest: function() {
        var watch, value, last,
            watchers,
            asyncQueue = this.$$asyncQueue,
            postDigestQueue = this.$$postDigestQueue,
            length,
            dirty, ttl = TTL,
            next, current, target = this,
            watchLog = [],
            logIdx, logMsg, asyncTask;

        beginPhase('$digest');//设置$$phase状态为$digest
        // Check for changes to browser url that happened in sync before the call to $digest
        $browser.$$checkUrlChange();

        lastDirtyWatch = null;

        do { // "while dirty" loop
          dirty = false;
          current = target;

        //首先执行由$scope.$evalAsync注册的异步对象    
          while(asyncQueue.length) {
            try {
              asyncTask = asyncQueue.shift();
              asyncTask.scope.$eval(asyncTask.expression);
            } catch (e) {
              clearPhase();
              $exceptionHandler(e);
            }
            lastDirtyWatch = null;
          }

          traverseScopesLoop:
          do { // 对当前$scope以及子$scope循环
            if ((watchers = current.$$watchers)) {
              // process our watches
              length = watchers.length;

              while (length--) {//从后往前遍历
                try {
                  watch = watchers[length];
                  // Most common watches are on primitives, in which case we can short
                  // circuit it with === operator, only when === fails do we use .equals
                  if (watch) {
                    //判断值是否改变
                    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;
                      lastDirtyWatch = watch;
                      watch.last = watch.eq ? copy(value, null) : value;
                      //执行watcher的监听函数
                      watch.fn(value, ((last === initWatchVal) ? value : last), current);
                      if (ttl < 5) {
                         //以下是日志信息
                        logIdx = 4 - ttl;
                        if (!watchLog[logIdx]) watchLog[logIdx] = [];
                        logMsg = (isFunction(watch.exp))
                            ? 'fn: ' + (watch.exp.name || watch.exp.toString())
                            : watch.exp;
                        logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
                        watchLog[logIdx].push(logMsg);
                      }
                    } else if (watch === lastDirtyWatch) {
                      // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
                      // have already been tested.
                      dirty = false;
                      break traverseScopesLoop;
                    }
                  }
                } catch (e) {
                  clearPhase();
                  $exceptionHandler(e);
                }
              }
            }

            // Insanity Warning: scope depth-first traversal(深度优先遍历)
            // yes, this code is a bit crazy, but it works and we have tests to prove it!
            // this piece should be kept in sync with the traversal in $broadcast
            //$$childHead 第一个子元素,$$nextSibling 邻近向后的兄弟节点 ,$$childTail最后一个子元素
            if (!(next = (current.$$childHead || 
                (current !== target && current.$$nextSibling)))) {
              while(current !== target && !(next = current.$$nextSibling)) {
                current = current.$parent;
              }
            }
          } while ((current = next));

          // `break traverseScopesLoop;` takes us to here

          if((dirty || asyncQueue.length) && !(ttl--)) {
           //如果超过默认10次的$digest的限制,那么终止$digest循环
            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);

        clearPhase();

        while(postDigestQueue.length) {//执行postDigest序列
          try {
            postDigestQueue.shift()();
          } catch (e) {
            $exceptionHandler(e);
          }
        }
      },

分析下主要的对象:
watcher:是数组,包含多个watch对象
这里写图片描述
这是watch对象的值:

        //rootScope.js -> $watch
        {
           fn: listener, //监听函数
           last: initWatchVal, // 上次的值
           get: get, //获取监听表达式的值
           exp: watchExp,//监听表达式
           eq: !!objectEquality//是否需要深度比对,$watch函数的第三个参数
         };

$watch和$watchCollection的区别

$watch$watchCollection都是监控值的变化。$watch有三个参数,第三个参数表明是否可以深检查,会检查对象是否完全相等,就是$digest中的equals比较。$watchCollection内部调用的是$watch,区别就是$watchCollection是浅监视,只检查对象或数组的第一层,对于更深层的不会再比较。所以当有时候使用$watchCollection效率会更高于$watch(expr,listener,true);

$apply,$eval,$evalAsync

$apply的作用是手动触发$digest循环,看下源码:

$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可以传入参数,返回执行$eval方法来解析表达式,最后调用$digest;来看下$eval方法:

 $eval: function(expr, locals) {
        return $parse(expr)(this, locals);
      },

可以看出$eval等同于$parse;
$evalAsync有着很大用处,首先看下源码:

$evalAsync: function(expr) {
        // if we are outside of an $digest loop and this is the first time we are scheduling async
        // task also schedule async auto-flush
        if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
          $browser.defer(function() {
            if ($rootScope.$$asyncQueue.length) {
              $rootScope.$digest();
            }
          });
        }

        this.$$asyncQueue.push({scope: this, expression: expr});
      }

如果当前不处于$digest或者$apply的过程中(只有在$apply$digest方法中才会设置$$phase这个字段),并且asyncQueue数组中还不存在任务时,就会异步调度一轮digest循环来确保asyncQueue数组中的表达式会被执行。如果目前已经处于一轮digest循环中,那么它能够确保你定义的任务在本轮digest循环期间一定会被执行!因此,这个过程和浏览器就没有任何关系了,这样能够提高浏览器的渲染效率,因为无效的渲染被屏蔽了。如果当前不在一轮digest循环中,和$timeout就几乎没有区别了。
关于$timeout$evalAsync,在Stackoverlow上有比较好的一个总结,简单的翻译一下:
1. 如果在directive中使用$evalAsync,那么它的运行时机在Angular对DOM进行操作之后,浏览器渲染之前。
2. 如果在controller中使用$evalAsync,那么它的运行时机在Angular对DOM进行操作之前,同时也在浏览器渲染之前 - 很少需要这样做。
3. 如果通过$timeout来异步执行代码,那么它的运行时机在Angular对DOM进行操作之后,也在浏览器渲染完毕之后(这也许会造成页面闪烁)。

因此,我们可以记住一个结论:使用$evalAsync的最佳场合是在指令的link方法中。这样能够避免浏览器不必要的渲染而造成的页面闪烁。当你在directive中考虑使用$timeout时,不妨试试$evalAsync

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值