From d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd Mon Sep 17 00:00:00 2001 From: Karl Seamon Date: Tue, 3 Dec 2013 19:16:08 -0500 Subject: 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 --- src/ng/rootScope.js | 53 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 17 deletions(-) (limited to 'src/ng/rootScope.js') 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(); -- cgit v1.2.3