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

Commit c1500ea

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 Closes: #5799
1 parent 4bc89bf commit c1500ea

File tree

4 files changed

+81
-5
lines changed

4 files changed

+81
-5
lines changed

benchmarks/largetable-bp/main.html

+9-2
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>attribute interpolation: <input type="radio" ng-model="benchmarkType" value="interpolationAttr"></div>
1819
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
1920
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
@@ -35,7 +36,7 @@ <h2>baseline binding</h2>
3536
</div>
3637
<div ng-switch-when="ngBindOnce">
3738
<h2>baseline binding once</h2>
38-
<div ng-repeat="row in data">
39+
<div ng-repeat="row in ::data">
3940
<span ng-repeat="column in ::row">
4041
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
4142
</span>
@@ -47,6 +48,12 @@ <h2>baseline interpolation</h2>
4748
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
4849
</div>
4950
</div>
51+
<div ng-switch-when="bindOnceInterpolation">
52+
<h2>baseline one-time interpolation</h2>
53+
<div ng-repeat="row in ::data">
54+
<span ng-repeat="column in ::row">{{::column.i}}:{{::column.j}}|</span>
55+
</div>
56+
</div>
5057
<div ng-switch-when="interpolationAttr">
5158
<h2>attribute interpolation</h2>
5259
<div ng-repeat="row in data">
@@ -80,4 +87,4 @@ <h2>interpolation with filter</h2>
8087
</ng-switch>
8188
</div>
8289
</div>
83-
</div>
90+
</div>

src/Angular.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ function arrayRemove(array, value) {
645645
var index = array.indexOf(value);
646646
if (index >= 0)
647647
array.splice(index, 1);
648-
return value;
648+
return index;
649649
}
650650

651651
/**

src/ng/rootScope.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ function $RootScopeProvider() {
135135
this.$$destroyed = false;
136136
this.$$listeners = {};
137137
this.$$listenerCount = {};
138+
this.$$watchersCount = 0;
138139
this.$$isolateBindings = null;
139140
}
140141

@@ -210,6 +211,7 @@ function $RootScopeProvider() {
210211
this.$$childHead = this.$$childTail = null;
211212
this.$$listeners = {};
212213
this.$$listenerCount = {};
214+
this.$$watchersCount = 0;
213215
this.$id = nextUid();
214216
this.$$ChildScope = null;
215217
};
@@ -384,9 +386,12 @@ function $RootScopeProvider() {
384386
// we use unshift since we use a while loop in $digest for speed.
385387
// the while loop reads in reverse order.
386388
array.unshift(watcher);
389+
incrementWatchersCount(this, 1);
387390

388391
return function deregisterWatch() {
389-
arrayRemove(array, watcher);
392+
if (arrayRemove(array, watcher) >= 0) {
393+
incrementWatchersCount(scope, -1);
394+
}
390395
lastDirtyWatch = null;
391396
};
392397
},
@@ -794,7 +799,7 @@ function $RootScopeProvider() {
794799
// Insanity Warning: scope depth-first traversal
795800
// yes, this code is a bit crazy, but it works and we have tests to prove it!
796801
// this piece should be kept in sync with the traversal in $broadcast
797-
if (!(next = (current.$$childHead ||
802+
if (!(next = ((current.$$watchersCount && current.$$childHead) ||
798803
(current !== target && current.$$nextSibling)))) {
799804
while (current !== target && !(next = current.$$nextSibling)) {
800805
current = current.$parent;
@@ -869,6 +874,7 @@ function $RootScopeProvider() {
869874
this.$$destroyed = true;
870875
if (this === $rootScope) return;
871876

877+
incrementWatchersCount(this, -this.$$watchersCount);
872878
for (var eventName in this.$$listenerCount) {
873879
decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
874880
}
@@ -1290,6 +1296,11 @@ function $RootScopeProvider() {
12901296
$rootScope.$$phase = null;
12911297
}
12921298

1299+
function incrementWatchersCount(current, count) {
1300+
do {
1301+
current.$$watchersCount += count;
1302+
} while ((current = current.$parent));
1303+
}
12931304

12941305
function decrementListenerCount(current, count, name) {
12951306
do {

test/ng/rootScopeSpec.js

+58
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)