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) {