Skip to content

Commit 55526ff

Browse files
committed
perf(Scope): limit propagation of $broadcast to scopes that have registered the event
Update $on and $destroy to maintain a count of event keys registered for each scope and its children. $broadcast will not descend past a node that has a count of 0/undefined for the $broadcasted event key. Resolves angular#5341
1 parent 043190f commit 55526ff

File tree

2 files changed

+83
-4
lines changed

2 files changed

+83
-4
lines changed

src/ng/rootScope.js

+27-4
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ function $RootScopeProvider(){
133133
this.$$asyncQueue = [];
134134
this.$$postDigestQueue = [];
135135
this.$$listeners = {};
136+
this.$$listenerCount = {};
136137
this.$$isolateBindings = {};
137138
}
138139

@@ -192,6 +193,7 @@ function $RootScopeProvider(){
192193
}
193194
child['this'] = child;
194195
child.$$listeners = {};
196+
child.$$listenerCount = {};
195197
child.$parent = this;
196198
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
197199
child.$$prevSibling = this.$$childTail;
@@ -696,6 +698,18 @@ function $RootScopeProvider(){
696698
this.$$destroyed = true;
697699
if (this === $rootScope) return;
698700

701+
var self = this;
702+
forEach(this.$$listenerCount, function(count, name) {
703+
var current = self;
704+
do {
705+
current.$$listenerCount[name] -= count;
706+
707+
if (current.$$listenerCount[name] === 0) {
708+
delete current.$$listenerCount[name];
709+
}
710+
} while ((current = current.$parent));
711+
});
712+
699713
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
700714
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
701715
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
@@ -885,6 +899,14 @@ function $RootScopeProvider(){
885899
}
886900
namedListeners.push(listener);
887901

902+
var current = this;
903+
do {
904+
if (!current.$$listenerCount[name]) {
905+
current.$$listenerCount[name] = 0;
906+
}
907+
current.$$listenerCount[name]++;
908+
} while ((current = current.$parent));
909+
888910
return function() {
889911
namedListeners[indexOf(namedListeners, listener)] = null;
890912
};
@@ -998,8 +1020,7 @@ function $RootScopeProvider(){
9981020
listeners, i, length;
9991021

10001022
//down while you can, then up and next sibling or up and next sibling until back at root
1001-
do {
1002-
current = next;
1023+
while ((current = next)) {
10031024
event.currentScope = current;
10041025
listeners = current.$$listeners[name] || [];
10051026
for (i=0, length = listeners.length; i<length; i++) {
@@ -1021,12 +1042,14 @@ function $RootScopeProvider(){
10211042
// Insanity Warning: scope depth-first traversal
10221043
// yes, this code is a bit crazy, but it works and we have tests to prove it!
10231044
// this piece should be kept in sync with the traversal in $digest
1024-
if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
1045+
// (though it differs due to having the extra check for $$listenerCount)
1046+
if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
1047+
(current !== target && current.$$nextSibling)))) {
10251048
while(current !== target && !(next = current.$$nextSibling)) {
10261049
current = current.$parent;
10271050
}
10281051
}
1029-
} while ((current = next));
1052+
}
10301053

10311054
return event;
10321055
}

test/ng/rootScopeSpec.js

+56
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,26 @@ describe('Scope', function() {
730730
first.$apply();
731731
expect(log).toBe('1232323');
732732
}));
733+
734+
it('should decrement anscestor $$listenerCount entries', inject(function($rootScope) {
735+
var EVENT = 'fooEvent',
736+
spy = jasmine.createSpy('listener'),
737+
firstSecond = first.$new();
738+
739+
firstSecond.$on(EVENT, spy);
740+
middle.$on(EVENT, spy);
741+
742+
expect($rootScope.$$listenerCount[EVENT]).toBe(2);
743+
expect(first.$$listenerCount[EVENT]).toBe(1);
744+
745+
firstSecond.$destroy();
746+
747+
expect($rootScope.$$listenerCount[EVENT]).toBe(1);
748+
expect(first.$$listenerCount[EVENT]).toBeUndefined();
749+
750+
$rootScope.$broadcast(EVENT);
751+
expect(spy.callCount).toBe(1);
752+
}));
733753
});
734754

735755

@@ -1114,6 +1134,25 @@ describe('Scope', function() {
11141134
child.$broadcast('abc');
11151135
expect(log).toEqual('');
11161136
}));
1137+
1138+
1139+
it('should increment ancestor $$listenerCount entries', inject(function($rootScope) {
1140+
var child1 = $rootScope.$new(),
1141+
child2 = child1.$new(),
1142+
spy = jasmine.createSpy();
1143+
1144+
$rootScope.$on('event1', spy);
1145+
expect($rootScope.$$listenerCount).toEqual({event1: 1});
1146+
1147+
child1.$on('event1', spy);
1148+
expect($rootScope.$$listenerCount).toEqual({event1: 2});
1149+
expect(child1.$$listenerCount).toEqual({event1: 1});
1150+
1151+
child2.$on('event2', spy);
1152+
expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1});
1153+
expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1});
1154+
expect(child2.$$listenerCount).toEqual({event2: 1});
1155+
}))
11171156
});
11181157

11191158

@@ -1358,6 +1397,23 @@ describe('Scope', function() {
13581397
$rootScope.$broadcast('fooEvent');
13591398
expect(log).toBe('');
13601399
}));
1400+
1401+
1402+
it('should not descend past scopes with a $$listerCount of 0 or undefined',
1403+
inject(function($rootScope) {
1404+
var EVENT = 'fooEvent',
1405+
spy = jasmine.createSpy('listener');
1406+
1407+
// Precondition: There should be no listeners for fooEvent.
1408+
expect($rootScope.$$listenerCount[EVENT]).toBeUndefined();
1409+
1410+
// Add a spy listener to a child scope.
1411+
$rootScope.$$childHead.$$listeners[EVENT] = [spy];
1412+
1413+
// $rootScope's count for 'fooEvent' is undefined, so spy should not be called.
1414+
$rootScope.$broadcast(EVENT);
1415+
expect(spy).not.toHaveBeenCalled();
1416+
}));
13611417

13621418

13631419
it('should return event object', function() {

0 commit comments

Comments
 (0)