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

perf($scope): Add a property $$watchersCount to scope #5799

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions benchmarks/largetable-bp/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
<div>interpolation + bind-once: <input type="radio" ng-model="benchmarkType" value="bindOnceInterpolation"></div>
<div>attribute interpolation: <input type="radio" ng-model="benchmarkType" value="interpolationAttr"></div>
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
Expand All @@ -35,7 +36,7 @@ <h2>baseline binding</h2>
</div>
<div ng-switch-when="ngBindOnce">
<h2>baseline binding once</h2>
<div ng-repeat="row in data">
<div ng-repeat="row in ::data">
<span ng-repeat="column in ::row">
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
</span>
Expand All @@ -47,6 +48,12 @@ <h2>baseline interpolation</h2>
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
</div>
</div>
<div ng-switch-when="bindOnceInterpolation">
<h2>baseline one-time interpolation</h2>
<div ng-repeat="row in ::data">
<span ng-repeat="column in ::row">{{::column.i}}:{{::column.j}}|</span>
</div>
</div>
<div ng-switch-when="interpolationAttr">
<h2>attribute interpolation</h2>
<div ng-repeat="row in data">
Expand Down Expand Up @@ -80,4 +87,4 @@ <h2>interpolation with filter</h2>
</ng-switch>
</div>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ function arrayRemove(array, value) {
var index = array.indexOf(value);
if (index >= 0)
array.splice(index, 1);
return value;
return index;
}

/**
Expand Down
15 changes: 13 additions & 2 deletions src/ng/rootScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ function $RootScopeProvider() {
this.$$destroyed = false;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$$isolateBindings = null;
}

Expand Down Expand Up @@ -210,6 +211,7 @@ function $RootScopeProvider() {
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$id = nextUid();
this.$$ChildScope = null;
};
Expand Down Expand Up @@ -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;
};
},
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 {
Expand Down
58 changes: 58 additions & 0 deletions test/ng/rootScopeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down