diff --git a/benchmarks/largetable-bp/main.html b/benchmarks/largetable-bp/main.html index f40353cb61e2..471a4441dd9e 100644 --- a/benchmarks/largetable-bp/main.html +++ b/benchmarks/largetable-bp/main.html @@ -14,6 +14,7 @@
ngBind:
ngBindOnce:
interpolation:
+
interpolation + bind-once:
attribute interpolation:
ngBind + fnInvocation:
interpolation + fnInvocation:
@@ -35,7 +36,7 @@

baseline binding

baseline binding once

-
+
:| @@ -47,6 +48,12 @@

baseline interpolation

{{column.i}}:{{column.j}}|
+
+

baseline one-time interpolation

+
+ {{::column.i}}:{{::column.j}}| +
+

attribute interpolation

@@ -80,4 +87,4 @@

interpolation with filter

-
\ No newline at end of file + diff --git a/src/Angular.js b/src/Angular.js index 52e74cbf396c..7aefdf784e2a 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -644,7 +644,7 @@ function arrayRemove(array, value) { var index = array.indexOf(value); if (index >= 0) array.splice(index, 1); - return value; + return index; } /** diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index be150faf1c15..f462381f88e6 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -135,6 +135,7 @@ function $RootScopeProvider() { this.$$destroyed = false; this.$$listeners = {}; this.$$listenerCount = {}; + this.$$watchersCount = 0; this.$$isolateBindings = null; } @@ -210,6 +211,7 @@ function $RootScopeProvider() { this.$$childHead = this.$$childTail = null; this.$$listeners = {}; this.$$listenerCount = {}; + this.$$watchersCount = 0; this.$id = nextUid(); this.$$ChildScope = null; }; @@ -384,9 +386,12 @@ function $RootScopeProvider() { // we use unshift since we use a while loop in $digest for speed. // the while loop reads in reverse order. array.unshift(watcher); + incrementWatchersCount(this, 1); return function deregisterWatch() { - arrayRemove(array, watcher); + if (arrayRemove(array, watcher) >= 0) { + incrementWatchersCount(scope, -1); + } lastDirtyWatch = null; }; }, @@ -794,7 +799,7 @@ 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 || + if (!(next = ((current.$$watchersCount && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; @@ -869,6 +874,7 @@ function $RootScopeProvider() { this.$$destroyed = true; if (this === $rootScope) return; + incrementWatchersCount(this, -this.$$watchersCount); for (var eventName in this.$$listenerCount) { decrementListenerCount(this, this.$$listenerCount[eventName], eventName); } @@ -1290,6 +1296,11 @@ function $RootScopeProvider() { $rootScope.$$phase = null; } + function incrementWatchersCount(current, count) { + do { + current.$$watchersCount += count; + } while ((current = current.$parent)); + } function decrementListenerCount(current, count, name) { do { diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 89d30820ff24..8fa9a00c7703 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -135,9 +135,67 @@ describe('Scope', function() { it('should not keep constant expressions on watch queue', inject(function($rootScope) { $rootScope.$watch('1 + 1', function() {}); expect($rootScope.$$watchers.length).toEqual(1); + expect($rootScope.$$watchersCount).toEqual(1); $rootScope.$digest(); expect($rootScope.$$watchers.length).toEqual(0); + expect($rootScope.$$watchersCount).toEqual(0); + })); + + it('should decrement the watcherCount when destroying a child scope', inject(function($rootScope) { + var child1 = $rootScope.$new(), + child2 = $rootScope.$new(), + grandChild1 = child1.$new(), + grandChild2 = child2.$new(); + + child1.$watch('a', function() {}); + child2.$watch('a', function() {}); + grandChild1.$watch('a', function() {}); + grandChild2.$watch('a', function() {}); + + expect($rootScope.$$watchersCount).toBe(4); + expect(child1.$$watchersCount).toBe(2); + expect(child2.$$watchersCount).toBe(2); + expect(grandChild1.$$watchersCount).toBe(1); + expect(grandChild2.$$watchersCount).toBe(1); + + grandChild2.$destroy(); + expect(child2.$$watchersCount).toBe(1); + expect($rootScope.$$watchersCount).toBe(3); + child1.$destroy(); + expect($rootScope.$$watchersCount).toBe(1); + })); + + it('should decrement the watcherCount when calling the remove function', inject(function($rootScope) { + var child1 = $rootScope.$new(), + child2 = $rootScope.$new(), + grandChild1 = child1.$new(), + grandChild2 = child2.$new(), + remove1, + remove2; + + remove1 = child1.$watch('a', function() {}); + child2.$watch('a', function() {}); + grandChild1.$watch('a', function() {}); + remove2 = grandChild2.$watch('a', function() {}); + + remove2(); + expect(grandChild2.$$watchersCount).toBe(0); + expect(child2.$$watchersCount).toBe(1); + expect($rootScope.$$watchersCount).toBe(3); + remove1(); + expect(grandChild1.$$watchersCount).toBe(1); + expect(child1.$$watchersCount).toBe(1); + expect($rootScope.$$watchersCount).toBe(2); + + // Execute everything a second time to be sure that calling the remove funciton + // several times, it only decrements the counter once + remove2(); + expect(child2.$$watchersCount).toBe(1); + expect($rootScope.$$watchersCount).toBe(2); + remove1(); + expect(child1.$$watchersCount).toBe(1); + expect($rootScope.$$watchersCount).toBe(2); })); it('should not keep constant literals on the watch queue', inject(function($rootScope) {