diff options
| author | Karl Seamon | 2013-12-03 19:16:08 -0500 | 
|---|---|---|
| committer | Igor Minar | 2013-12-05 15:37:37 -0800 | 
| commit | d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd (patch) | |
| tree | 7e92e3b9f74e53e301ef1e86cb899e1feb76baa3 /src/ng/rootScope.js | |
| parent | 09648e4888896948b733febb9c8652a36f8a22dd (diff) | |
| download | angular.js-d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd.tar.bz2 | |
chore(Scope): short-circuit after dirty-checking last dirty watcher
Stop dirty-checking during $digest after the last dirty watcher has been re-checked.
This prevents unneeded re-checking of the remaining watchers (They were already
checked in the previous iteration), bringing a substantial performance improvement
to the average case run time of $digest.
Closes #5272
Closes #5287
Diffstat (limited to 'src/ng/rootScope.js')
| -rw-r--r-- | src/ng/rootScope.js | 53 | 
1 files changed, 36 insertions, 17 deletions
| diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index a54fdc98..233dfa3b 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -71,6 +71,7 @@  function $RootScopeProvider(){    var TTL = 10;    var $rootScopeMinErr = minErr('$rootScope'); +  var lastDirtyWatch = null;    this.digestTtl = function(value) {      if (arguments.length) { @@ -325,6 +326,8 @@ function $RootScopeProvider(){                eq: !!objectEquality              }; +        lastDirtyWatch = null; +          // 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'); @@ -553,6 +556,8 @@ function $RootScopeProvider(){          beginPhase('$digest'); +        lastDirtyWatch = null; +          do { // "while dirty" loop            dirty = false;            current = target; @@ -565,8 +570,10 @@ function $RootScopeProvider(){                clearPhase();                $exceptionHandler(e);              } +            lastDirtyWatch = null;            } +          traverseScopesLoop:            do { // "traverse the scopes" loop              if ((watchers = current.$$watchers)) {                // process our watches @@ -576,22 +583,30 @@ function $RootScopeProvider(){                    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); +                  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) : 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); +                      } +                    } 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) { @@ -604,13 +619,16 @@ function $RootScopeProvider(){              // 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)))) { +            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 && !(ttl--)) {              clearPhase();              throw $rootScopeMinErr('infdig', @@ -618,6 +636,7 @@ function $RootScopeProvider(){                  'Watchers fired in the last 5 iterations: {1}',                  TTL, toJson(watchLog));            } +          } while (dirty || asyncQueue.length);          clearPhase(); | 
