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

Commit 014b38b

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 5cf1d89 commit 014b38b

File tree

4 files changed

+87
-4
lines changed

4 files changed

+87
-4
lines changed

benchmarks/largetable-bp/main.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
<div>baseline binding: <input type=radio ng-model="benchmarkType" value="baselineBinding"></div>
1313
<div>baseline interpolation: <input type=radio ng-model="benchmarkType" value="baselineInterpolation"></div>
1414
<div>ngBind: <input type=radio ng-model="benchmarkType" value="ngBind"></div>
15+
<div>ngBind with one-time binding: <input type=radio ng-model="benchmarkType" value="ngBindOnce"></div>
1516
<div>interpolation: <input type=radio ng-model="benchmarkType" value="interpolation"></div>
17+
<div>interpolation with one-time binding: <input type=radio ng-model="benchmarkType" value="bindOnceInterpolation"></div>
1618
<div>ngBind + fnInvocation: <input type=radio ng-model="benchmarkType" value="ngBindFn"></div>
1719
<div>interpolation + fnInvocation: <input type=radio ng-model="benchmarkType" value="interpolationFn"></div>
1820
<div>ngBind + filter: <input type=radio ng-model="benchmarkType" value="ngBindFilter"></div>
@@ -29,12 +31,24 @@ <h2>baseline binding</h2>
2931
<span ng-repeat="column in row"><span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|</span>
3032
</div>
3133
</div>
34+
<div ng-switch-when="ngBindOnce">
35+
<h2>baseline one-time binding</h2>
36+
<div ng-repeat="row in ::data">
37+
<span ng-repeat="column in ::row"><span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|</span>
38+
</div>
39+
</div>
3240
<div ng-switch-when="interpolation">
3341
<h2>baseline interpolation</h2>
3442
<div ng-repeat="row in data">
3543
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
3644
</div>
3745
</div>
46+
<div ng-switch-when="bindOnceInterpolation">
47+
<h2>baseline one-time interpolation</h2>
48+
<div ng-repeat="row in ::data">
49+
<span ng-repeat="column in ::row">{{::column.i}}:{{::column.j}}|</span>
50+
</div>
51+
</div>
3852
<div ng-switch-when="ngBindFn">
3953
<h2>bindings with functions</h2>
4054
<div ng-repeat="row in data">
@@ -62,4 +76,4 @@ <h2>interpolation with filter</h2>
6276
</ng-switch>
6377
</div>
6478
</div>
65-
</div>
79+
</div>

src/Angular.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ function arrayRemove(array, value) {
661661
var index = array.indexOf(value);
662662
if (index >=0)
663663
array.splice(index, 1);
664-
return value;
664+
return index;
665665
}
666666

667667
function isLeafNode (node) {

src/ng/rootScope.js

Lines changed: 13 additions & 2 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 = null;
138139
this.$$applyAsyncQueue = [];
139140
}
@@ -205,6 +206,7 @@ function $RootScopeProvider(){
205206
this.$$childHead = this.$$childTail = null;
206207
this.$$listeners = {};
207208
this.$$listenerCount = {};
209+
this.$$watchersCount = 0;
208210
this.$id = nextUid();
209211
this.$$ChildScope = null;
210212
};
@@ -368,9 +370,12 @@ function $RootScopeProvider(){
368370
// we use unshift since we use a while loop in $digest for speed.
369371
// the while loop reads in reverse order.
370372
array.unshift(watcher);
373+
incrementWatchersCount(this, 1);
371374

372375
return function deregisterWatch() {
373-
arrayRemove(array, watcher);
376+
if (arrayRemove(array, watcher) >= 0) {
377+
incrementWatchersCount(scope, -1);
378+
}
374379
lastDirtyWatch = null;
375380
};
376381
},
@@ -775,7 +780,7 @@ function $RootScopeProvider(){
775780
// Insanity Warning: scope depth-first traversal
776781
// yes, this code is a bit crazy, but it works and we have tests to prove it!
777782
// this piece should be kept in sync with the traversal in $broadcast
778-
if (!(next = (current.$$childHead ||
783+
if (!(next = ((current.$$watchersCount && current.$$childHead) ||
779784
(current !== target && current.$$nextSibling)))) {
780785
while(current !== target && !(next = current.$$nextSibling)) {
781786
current = current.$parent;
@@ -850,6 +855,7 @@ function $RootScopeProvider(){
850855
this.$$destroyed = true;
851856
if (this === $rootScope) return;
852857

858+
incrementWatchersCount(this, -this.$$watchersCount);
853859
for (var eventName in this.$$listenerCount) {
854860
decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
855861
}
@@ -1266,6 +1272,11 @@ function $RootScopeProvider(){
12661272
$rootScope.$$phase = null;
12671273
}
12681274

1275+
function incrementWatchersCount(current, count) {
1276+
do {
1277+
current.$$watchersCount += count;
1278+
} while ((current = current.$parent));
1279+
}
12691280

12701281
function decrementListenerCount(current, count, name) {
12711282
do {

test/ng/rootScopeSpec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,67 @@ describe('Scope', function() {
109109
it('should not keep constant expressions on watch queue', inject(function($rootScope) {
110110
$rootScope.$watch('1 + 1', function() {});
111111
expect($rootScope.$$watchers.length).toEqual(1);
112+
expect($rootScope.$$watchersCount).toEqual(1);
112113
$rootScope.$digest();
113114

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

117175
it('should not keep constant literals on the watch queue', inject(function($rootScope) {

0 commit comments

Comments
 (0)