Skip to content

Commit

Permalink
perf(Scope): limit propagation of $broadcast to scopes that have regi…
Browse files Browse the repository at this point in the history
…stered 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
  • Loading branch information
kseamon committed Dec 27, 2013
1 parent 043190f commit 55526ff
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 4 deletions.
31 changes: 27 additions & 4 deletions src/ng/rootScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ function $RootScopeProvider(){
this.$$asyncQueue = [];
this.$$postDigestQueue = [];
this.$$listeners = {};
this.$$listenerCount = {};
this.$$isolateBindings = {};
}

Expand Down Expand Up @@ -192,6 +193,7 @@ function $RootScopeProvider(){
}
child['this'] = child;
child.$$listeners = {};
child.$$listenerCount = {};
child.$parent = this;
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
child.$$prevSibling = this.$$childTail;
Expand Down Expand Up @@ -696,6 +698,18 @@ function $RootScopeProvider(){
this.$$destroyed = true;
if (this === $rootScope) return;

var self = this;
forEach(this.$$listenerCount, function(count, name) {
var current = self;
do {
current.$$listenerCount[name] -= count;

if (current.$$listenerCount[name] === 0) {
delete current.$$listenerCount[name];
}
} while ((current = current.$parent));
});

if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
Expand Down Expand Up @@ -885,6 +899,14 @@ function $RootScopeProvider(){
}
namedListeners.push(listener);

var current = this;
do {
if (!current.$$listenerCount[name]) {
current.$$listenerCount[name] = 0;
}
current.$$listenerCount[name]++;
} while ((current = current.$parent));

return function() {
namedListeners[indexOf(namedListeners, listener)] = null;
};
Expand Down Expand Up @@ -998,8 +1020,7 @@ function $RootScopeProvider(){
listeners, i, length;

//down while you can, then up and next sibling or up and next sibling until back at root
do {
current = next;
while ((current = next)) {
event.currentScope = current;
listeners = current.$$listeners[name] || [];
for (i=0, length = listeners.length; i<length; i++) {
Expand All @@ -1021,12 +1042,14 @@ 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 $digest
if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
// (though it differs due to having the extra check for $$listenerCount)
if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
(current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));
}

return event;
}
Expand Down
56 changes: 56 additions & 0 deletions test/ng/rootScopeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,26 @@ describe('Scope', function() {
first.$apply();
expect(log).toBe('1232323');
}));

it('should decrement anscestor $$listenerCount entries', inject(function($rootScope) {
var EVENT = 'fooEvent',
spy = jasmine.createSpy('listener'),
firstSecond = first.$new();

firstSecond.$on(EVENT, spy);
middle.$on(EVENT, spy);

expect($rootScope.$$listenerCount[EVENT]).toBe(2);
expect(first.$$listenerCount[EVENT]).toBe(1);

firstSecond.$destroy();

expect($rootScope.$$listenerCount[EVENT]).toBe(1);
expect(first.$$listenerCount[EVENT]).toBeUndefined();

$rootScope.$broadcast(EVENT);
expect(spy.callCount).toBe(1);
}));
});


Expand Down Expand Up @@ -1114,6 +1134,25 @@ describe('Scope', function() {
child.$broadcast('abc');
expect(log).toEqual('');
}));


it('should increment ancestor $$listenerCount entries', inject(function($rootScope) {
var child1 = $rootScope.$new(),
child2 = child1.$new(),
spy = jasmine.createSpy();

$rootScope.$on('event1', spy);
expect($rootScope.$$listenerCount).toEqual({event1: 1});

child1.$on('event1', spy);
expect($rootScope.$$listenerCount).toEqual({event1: 2});
expect(child1.$$listenerCount).toEqual({event1: 1});

child2.$on('event2', spy);
expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1});
expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1});
expect(child2.$$listenerCount).toEqual({event2: 1});
}))
});


Expand Down Expand Up @@ -1358,6 +1397,23 @@ describe('Scope', function() {
$rootScope.$broadcast('fooEvent');
expect(log).toBe('');
}));


it('should not descend past scopes with a $$listerCount of 0 or undefined',
inject(function($rootScope) {
var EVENT = 'fooEvent',
spy = jasmine.createSpy('listener');

// Precondition: There should be no listeners for fooEvent.
expect($rootScope.$$listenerCount[EVENT]).toBeUndefined();

// Add a spy listener to a child scope.
$rootScope.$$childHead.$$listeners[EVENT] = [spy];

// $rootScope's count for 'fooEvent' is undefined, so spy should not be called.
$rootScope.$broadcast(EVENT);
expect(spy).not.toHaveBeenCalled();
}));


it('should return event object', function() {
Expand Down

0 comments on commit 55526ff

Please sign in to comment.