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

Commit 9c36532

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 2a0254e commit 9c36532

File tree

4 files changed

+81
-5
lines changed

4 files changed

+81
-5
lines changed

benchmarks/largetable-bp/main.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
1515
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
1616
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
17+
<div>interpolation + bind-once: <input type="radio" ng-model="benchmarkType" value="bindOnceInterpolation"></div>
1718
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
1819
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
1920
<div>ngBind + filter: <input type="radio" ng-model="benchmarkType" value="ngBindFilter"></div>
@@ -34,7 +35,7 @@ <h2>baseline binding</h2>
3435
</div>
3536
<div ng-switch-when="ngBindOnce">
3637
<h2>baseline binding once</h2>
37-
<div ng-repeat="row in data">
38+
<div ng-repeat="row in ::data">
3839
<span ng-repeat="column in ::row">
3940
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
4041
</span>
@@ -46,6 +47,12 @@ <h2>baseline interpolation</h2>
4647
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
4748
</div>
4849
</div>
50+
<div ng-switch-when="bindOnceInterpolation">
51+
<h2>baseline one-time interpolation</h2>
52+
<div ng-repeat="row in ::data">
53+
<span ng-repeat="column in ::row">{{::column.i}}:{{::column.j}}|</span>
54+
</div>
55+
</div>
4956
<div ng-switch-when="ngBindFn">
5057
<h2>bindings with functions</h2>
5158
<div ng-repeat="row in data">
@@ -73,4 +80,4 @@ <h2>interpolation with filter</h2>
7380
</ng-switch>
7481
</div>
7582
</div>
76-
</div>
83+
</div>

src/Angular.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ function arrayRemove(array, value) {
636636
var index = array.indexOf(value);
637637
if (index >=0)
638638
array.splice(index, 1);
639-
return value;
639+
return index;
640640
}
641641

642642
/**

src/ng/rootScope.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ function $RootScopeProvider() {
132132
this.$$destroyed = false;
133133
this.$$listeners = {};
134134
this.$$listenerCount = {};
135+
this.$$watchersCount = 0;
135136
this.$$isolateBindings = null;
136137
}
137138

@@ -207,6 +208,7 @@ function $RootScopeProvider() {
207208
this.$$childHead = this.$$childTail = null;
208209
this.$$listeners = {};
209210
this.$$listenerCount = {};
211+
this.$$watchersCount = 0;
210212
this.$id = nextUid();
211213
this.$$ChildScope = null;
212214
};
@@ -381,9 +383,12 @@ function $RootScopeProvider() {
381383
// we use unshift since we use a while loop in $digest for speed.
382384
// the while loop reads in reverse order.
383385
array.unshift(watcher);
386+
incrementWatchersCount(this, 1);
384387

385388
return function deregisterWatch() {
386-
arrayRemove(array, watcher);
389+
if (arrayRemove(array, watcher) >= 0) {
390+
incrementWatchersCount(scope, -1);
391+
}
387392
lastDirtyWatch = null;
388393
};
389394
},
@@ -788,7 +793,7 @@ function $RootScopeProvider() {
788793
// Insanity Warning: scope depth-first traversal
789794
// yes, this code is a bit crazy, but it works and we have tests to prove it!
790795
// this piece should be kept in sync with the traversal in $broadcast
791-
if (!(next = (current.$$childHead ||
796+
if (!(next = ((current.$$watchersCount && current.$$childHead) ||
792797
(current !== target && current.$$nextSibling)))) {
793798
while (current !== target && !(next = current.$$nextSibling)) {
794799
current = current.$parent;
@@ -863,6 +868,7 @@ function $RootScopeProvider() {
863868
this.$$destroyed = true;
864869
if (this === $rootScope) return;
865870

871+
incrementWatchersCount(this, -this.$$watchersCount);
866872
for (var eventName in this.$$listenerCount) {
867873
decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
868874
}
@@ -1283,6 +1289,11 @@ function $RootScopeProvider() {
12831289
$rootScope.$$phase = null;
12841290
}
12851291

1292+
function incrementWatchersCount(current, count) {
1293+
do {
1294+
current.$$watchersCount += count;
1295+
} while ((current = current.$parent));
1296+
}
12861297

12871298
function decrementListenerCount(current, count, name) {
12881299
do {

test/ng/rootScopeSpec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,67 @@ describe('Scope', function() {
135135
it('should not keep constant expressions on watch queue', inject(function($rootScope) {
136136
$rootScope.$watch('1 + 1', function() {});
137137
expect($rootScope.$$watchers.length).toEqual(1);
138+
expect($rootScope.$$watchersCount).toEqual(1);
138139
$rootScope.$digest();
139140

140141
expect($rootScope.$$watchers.length).toEqual(0);
142+
expect($rootScope.$$watchersCount).toEqual(0);
143+
}));
144+
145+
it('should decrement the watcherCount when destroying a child scope', inject(function($rootScope) {
146+
var child1 = $rootScope.$new(),
147+
child2 = $rootScope.$new(),
148+
grandChild1 = child1.$new(),
149+
grandChild2 = child2.$new();
150+
151+
child1.$watch('a', function() {});
152+
child2.$watch('a', function() {});
153+
grandChild1.$watch('a', function() {});
154+
grandChild2.$watch('a', function() {});
155+
156+
expect($rootScope.$$watchersCount).toBe(4);
157+
expect(child1.$$watchersCount).toBe(2);
158+
expect(child2.$$watchersCount).toBe(2);
159+
expect(grandChild1.$$watchersCount).toBe(1);
160+
expect(grandChild2.$$watchersCount).toBe(1);
161+
162+
grandChild2.$destroy();
163+
expect(child2.$$watchersCount).toBe(1);
164+
expect($rootScope.$$watchersCount).toBe(3);
165+
child1.$destroy();
166+
expect($rootScope.$$watchersCount).toBe(1);
167+
}));
168+
169+
it('should decrement the watcherCount when calling the remove function', inject(function($rootScope) {
170+
var child1 = $rootScope.$new(),
171+
child2 = $rootScope.$new(),
172+
grandChild1 = child1.$new(),
173+
grandChild2 = child2.$new(),
174+
remove1,
175+
remove2;
176+
177+
remove1 = child1.$watch('a', function() {});
178+
child2.$watch('a', function() {});
179+
grandChild1.$watch('a', function() {});
180+
remove2 = grandChild2.$watch('a', function() {});
181+
182+
remove2();
183+
expect(grandChild2.$$watchersCount).toBe(0);
184+
expect(child2.$$watchersCount).toBe(1);
185+
expect($rootScope.$$watchersCount).toBe(3);
186+
remove1();
187+
expect(grandChild1.$$watchersCount).toBe(1);
188+
expect(child1.$$watchersCount).toBe(1);
189+
expect($rootScope.$$watchersCount).toBe(2);
190+
191+
// Execute everything a second time to be sure that calling the remove funciton
192+
// several times, it only decrements the counter once
193+
remove2();
194+
expect(child2.$$watchersCount).toBe(1);
195+
expect($rootScope.$$watchersCount).toBe(2);
196+
remove1();
197+
expect(child1.$$watchersCount).toBe(1);
198+
expect($rootScope.$$watchersCount).toBe(2);
141199
}));
142200

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

0 commit comments

Comments
 (0)