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 | |
| 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')
| -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(); |
