Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 57276b9

Browse files
committed
perf($scope): Add a property $$watchersCount to scope
Add a property $$watchersCount to scope that keeps the number of watchers in the scope plus all the child scopes. Use this property when traversing scopes looking for watches
1 parent 02a4582 commit 57276b9

File tree

3 files changed

+77
-5
lines changed

3 files changed

+77
-5
lines changed

src/Angular.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ function arrayRemove(array, value) {
651651
var index = indexOf(array, value);
652652
if (index >=0)
653653
array.splice(index, 1);
654-
return value;
654+
return index >=0;
655655
}
656656

657657
function isLeafNode (node) {

src/ng/rootScope.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ function $RootScopeProvider(){
134134
this.$$postDigestQueue = [];
135135
this.$$listeners = {};
136136
this.$$listenerCount = {};
137+
this.$$watchersCount = 0;
137138
this.$$isolateBindings = {};
138139
}
139140

@@ -196,6 +197,7 @@ function $RootScopeProvider(){
196197
child.$$listenerCount = {};
197198
child.$parent = this;
198199
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
200+
child.$$watchersCount = 0;
199201
child.$$prevSibling = this.$$childTail;
200202
if (this.$$childHead) {
201203
this.$$childTail.$$nextSibling = child;
@@ -340,7 +342,9 @@ function $RootScopeProvider(){
340342
var originalFn = watcher.fn;
341343
watcher.fn = function(newVal, oldVal, scope) {
342344
originalFn.call(this, newVal, oldVal, scope);
343-
arrayRemove(array, watcher);
345+
if (arrayRemove(array, watcher)) {
346+
incrementWatchersCount(scope, -1);
347+
}
344348
};
345349
}
346350

@@ -350,10 +354,13 @@ function $RootScopeProvider(){
350354
// we use unshift since we use a while loop in $digest for speed.
351355
// the while loop reads in reverse order.
352356
array.unshift(watcher);
357+
incrementWatchersCount(this, 1);
353358

354359
return function() {
355-
arrayRemove(array, watcher);
356-
lastDirtyWatch = null;
360+
if (arrayRemove(array, watcher)) {
361+
incrementWatchersCount(scope, -1);
362+
lastDirtyWatch = null;
363+
}
357364
};
358365
},
359366

@@ -622,7 +629,8 @@ function $RootScopeProvider(){
622629
// Insanity Warning: scope depth-first traversal
623630
// yes, this code is a bit crazy, but it works and we have tests to prove it!
624631
// this piece should be kept in sync with the traversal in $broadcast
625-
if (!(next = (current.$$childHead ||
632+
// (though it differs due to having the extra check for $$watchersCount)
633+
if (!(next = ((current.$$watchersCount && current.$$childHead) ||
626634
(current !== target && current.$$nextSibling)))) {
627635
while(current !== target && !(next = current.$$nextSibling)) {
628636
current = current.$parent;
@@ -699,6 +707,7 @@ function $RootScopeProvider(){
699707
this.$$destroyed = true;
700708
if (this === $rootScope) return;
701709

710+
incrementWatchersCount(this, -this.$$watchersCount);
702711
forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
703712

704713
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
@@ -1071,6 +1080,12 @@ function $RootScopeProvider(){
10711080
return fn;
10721081
}
10731082

1083+
function incrementWatchersCount(current, count) {
1084+
do {
1085+
current.$$watchersCount += count;
1086+
} while ((current = current.$parent));
1087+
}
1088+
10741089
function decrementListenerCount(current, count, name) {
10751090
do {
10761091
current.$$listenerCount[name] -= count;

test/ng/rootScopeSpec.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,68 @@ describe('Scope', function() {
108108
it('should not keep constant expressions on watch queue', inject(function($rootScope) {
109109
$rootScope.$watch('1 + 1', function() {});
110110
expect($rootScope.$$watchers.length).toEqual(1);
111+
expect($rootScope.$$watchersCount).toEqual(1);
111112
$rootScope.$digest();
112113

113114
expect($rootScope.$$watchers.length).toEqual(0);
115+
expect($rootScope.$$watchersCount).toEqual(0);
114116
}));
115117

118+
it('should decrement the watcherCount when destroying a child scope', inject(function($rootScope) {
119+
var child1 = $rootScope.$new(),
120+
child2 = $rootScope.$new(),
121+
grandChild1 = child1.$new(),
122+
grandChild2 = child2.$new();
123+
124+
child1.$watch('a', function() {});
125+
child2.$watch('a', function() {});
126+
grandChild1.$watch('a', function() {});
127+
grandChild2.$watch('a', function() {});
128+
129+
expect($rootScope.$$watchersCount).toBe(4);
130+
expect(child1.$$watchersCount).toBe(2);
131+
expect(child2.$$watchersCount).toBe(2);
132+
expect(grandChild1.$$watchersCount).toBe(1);
133+
expect(grandChild2.$$watchersCount).toBe(1);
134+
135+
grandChild2.$destroy();
136+
expect(child2.$$watchersCount).toBe(1);
137+
expect($rootScope.$$watchersCount).toBe(3);
138+
child1.$destroy();
139+
expect($rootScope.$$watchersCount).toBe(1);
140+
}));
141+
142+
it('should decrement the watcherCount when calling the remove function', inject(function($rootScope) {
143+
var child1 = $rootScope.$new(),
144+
child2 = $rootScope.$new(),
145+
grandChild1 = child1.$new(),
146+
grandChild2 = child2.$new(),
147+
remove1,
148+
remove2;
149+
150+
remove1 = child1.$watch('a', function() {});
151+
child2.$watch('a', function() {});
152+
grandChild1.$watch('a', function() {});
153+
remove2 = grandChild2.$watch('a', function() {});
154+
155+
remove2();
156+
expect(grandChild2.$$watchersCount).toBe(0);
157+
expect(child2.$$watchersCount).toBe(1);
158+
expect($rootScope.$$watchersCount).toBe(3);
159+
remove1();
160+
expect(grandChild1.$$watchersCount).toBe(1);
161+
expect(child1.$$watchersCount).toBe(1);
162+
expect($rootScope.$$watchersCount).toBe(2);
163+
164+
// Execute everything a second time to be sure that calling the remove funciton
165+
// several times, it only decrements the counter once
166+
remove2();
167+
expect(child2.$$watchersCount).toBe(1);
168+
expect($rootScope.$$watchersCount).toBe(2);
169+
remove1();
170+
expect(child1.$$watchersCount).toBe(1);
171+
expect($rootScope.$$watchersCount).toBe(2);
172+
}));
116173

117174
it('should delegate exceptions', function() {
118175
module(function($exceptionHandlerProvider) {

0 commit comments

Comments
 (0)