• AngularJs双向绑定详解


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

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

    一、$scope.$watch()

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

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

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

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

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

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

    $watch: function(watchExp, listener, objectEquality) {
            var scope = this,
                get = compileToFn(watchExp, 'watch'),
                array = scope.$$watchers,
                watcher = {
                  fn: listener,
                  last: initWatchVal,
                  get: get,
                  exp: watchExp,
                  eq: !!objectEquality
                };
    
            // in the case user pass string, we need to compile it, do we really need this ?
            if (!isFunction(listener)) {
              var listenFn = compileToFn(listener || noop, 'listener');
              watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
            }
    
            if (typeof watchExp == 'string' && get.constant) {
              var originalFn = watcher.fn;
              watcher.fn = function(newVal, oldVal, scope) {
                originalFn.call(this, newVal, oldVal, scope);
                arrayRemove(array, watcher);
              };
            }
    
            if (!array) {
              array = scope.$$watchers = [];
            }
            // we use unshift since we use a while loop in $digest for speed.
            // the while loop reads in reverse order.
            array.unshift(watcher);
    
            return function() {
              arrayRemove(array, watcher);
            };
          },

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

    二、$scope.$digest()

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

    $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');
    
            do { // "while dirty" loop
              dirty = false;
              current = target;
    
              while(asyncQueue.length) {
                try {
                  asyncTask = asyncQueue.shift();
                  asyncTask.scope.$eval(asyncTask.expression);
                } catch (e) {
                  $exceptionHandler(e);
                }
              }
    
              do { // "traverse the scopes" loop
                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 && (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.last = watch.eq ? copy(value) : value;
                        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);
                        }
                      }
                    } catch (e) {
                      $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
                if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
                  while(current !== target && !(next = current.$$nextSibling)) {
                    current = current.$parent;
                  }
                }
              } while ((current = next));
    
              if(dirty && !(ttl--)) {
                clearPhase();
                throw $rootScopeMinErr('infdig',
                    '{0} $digest() iterations reached. Aborting!
    Watchers fired in the last 5 iterations: {1}',
                    TTL, toJson(watchLog));
              }
            } while (dirty || asyncQueue.length);
    
            clearPhase();
    
            while(postDigestQueue.length) {
              try {
                postDigestQueue.shift()();
              } catch (e) {
                $exceptionHandler(e);
              }
            }
          },

    三、$scope.$apply()

      $apply使用函数作参数,它用$eval执行这个函数,然后通过$digest触发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方法里其实是调用了digest方法的,那么为什么要多增加一个apply来调用digest呢,可以看到这段代码中并没有直接调用digest而是首先进行了对expr的检验,也就是eval方法,这个方法如果校验不通过,是会抛出异常的,而angular并不推荐外部直接调用digest,所以就增加了apply方法来间接调用。

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

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

      引用网上一张图来解释

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

  • 相关阅读:
    深入nginx之《获取用户的真实IP》
    深入Nginx之《常用参数配置技巧》
    深入Nginx之《HTTP请求报文与HTTP响应报文》
    webapck html-loader 静态html模块化
    webpack四个基础概念
    从原生Android 跳转到hbuilder项目
    移动端适配方案 flexible.js
    vue使用px2rem
    koa2 post请求ctx.request.body空获取不到的解决办法
    url、href、src
  • 原文地址:https://www.cnblogs.com/shytong/p/5023988.html
Copyright © 2020-2023  润新知