mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-19 08:00:23 +00:00
perf(Scope): limit propagation of $broadcast to scopes that have listeners for 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. Closes #5341 Closes #5371
This commit is contained in:
parent
498365f219
commit
80e7a45584
2 changed files with 136 additions and 23 deletions
|
|
@ -133,6 +133,7 @@ function $RootScopeProvider(){
|
|||
this.$$asyncQueue = [];
|
||||
this.$$postDigestQueue = [];
|
||||
this.$$listeners = {};
|
||||
this.$$listenerCount = {};
|
||||
this.$$isolateBindings = {};
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -696,6 +698,8 @@ function $RootScopeProvider(){
|
|||
this.$$destroyed = true;
|
||||
if (this === $rootScope) return;
|
||||
|
||||
forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
|
||||
|
||||
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
|
||||
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
|
||||
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
|
||||
|
|
@ -885,8 +889,18 @@ function $RootScopeProvider(){
|
|||
}
|
||||
namedListeners.push(listener);
|
||||
|
||||
var current = this;
|
||||
do {
|
||||
if (!current.$$listenerCount[name]) {
|
||||
current.$$listenerCount[name] = 0;
|
||||
}
|
||||
current.$$listenerCount[name]++;
|
||||
} while ((current = current.$parent));
|
||||
|
||||
var self = this;
|
||||
return function() {
|
||||
namedListeners[indexOf(namedListeners, listener)] = null;
|
||||
decrementListenerCount(self, 1, name);
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -998,8 +1012,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++) {
|
||||
|
|
@ -1021,12 +1034,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;
|
||||
}
|
||||
|
|
@ -1055,6 +1070,16 @@ function $RootScopeProvider(){
|
|||
return fn;
|
||||
}
|
||||
|
||||
function decrementListenerCount(current, count, name) {
|
||||
do {
|
||||
current.$$listenerCount[name] -= count;
|
||||
|
||||
if (current.$$listenerCount[name] === 0) {
|
||||
delete current.$$listenerCount[name];
|
||||
}
|
||||
} while ((current = current.$parent));
|
||||
}
|
||||
|
||||
/**
|
||||
* function used as an initial value for watchers.
|
||||
* because it's unique we can easily tell it apart from other values
|
||||
|
|
|
|||
|
|
@ -730,6 +730,28 @@ 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);
|
||||
firstSecond.$on(EVENT, spy);
|
||||
middle.$on(EVENT, spy);
|
||||
|
||||
expect($rootScope.$$listenerCount[EVENT]).toBe(3);
|
||||
expect(first.$$listenerCount[EVENT]).toBe(2);
|
||||
|
||||
firstSecond.$destroy();
|
||||
|
||||
expect($rootScope.$$listenerCount[EVENT]).toBe(1);
|
||||
expect(first.$$listenerCount[EVENT]).toBeUndefined();
|
||||
|
||||
$rootScope.$broadcast(EVENT);
|
||||
expect(spy.callCount).toBe(1);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -1091,29 +1113,78 @@ describe('Scope', function() {
|
|||
}));
|
||||
|
||||
|
||||
it('should return a function that deregisters the listener', inject(function($rootScope) {
|
||||
var log = '',
|
||||
child = $rootScope.$new(),
|
||||
listenerRemove;
|
||||
it('should increment ancestor $$listenerCount entries', inject(function($rootScope) {
|
||||
var child1 = $rootScope.$new(),
|
||||
child2 = child1.$new(),
|
||||
spy = jasmine.createSpy();
|
||||
|
||||
function eventFn() {
|
||||
log += 'X';
|
||||
}
|
||||
$rootScope.$on('event1', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 1});
|
||||
|
||||
listenerRemove = child.$on('abc', eventFn);
|
||||
expect(log).toEqual('');
|
||||
expect(listenerRemove).toBeDefined();
|
||||
child1.$on('event1', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 2});
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1});
|
||||
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('XX');
|
||||
|
||||
log = '';
|
||||
listenerRemove();
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('');
|
||||
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});
|
||||
}));
|
||||
|
||||
|
||||
describe('deregistration', function() {
|
||||
|
||||
it('should return a function that deregisters the listener', inject(function($rootScope) {
|
||||
var log = '',
|
||||
child = $rootScope.$new(),
|
||||
listenerRemove;
|
||||
|
||||
function eventFn() {
|
||||
log += 'X';
|
||||
}
|
||||
|
||||
listenerRemove = child.$on('abc', eventFn);
|
||||
expect(log).toEqual('');
|
||||
expect(listenerRemove).toBeDefined();
|
||||
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('XX');
|
||||
expect($rootScope.$$listenerCount['abc']).toBe(1);
|
||||
|
||||
log = '';
|
||||
listenerRemove();
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('');
|
||||
expect($rootScope.$$listenerCount['abc']).toBeUndefined();
|
||||
}));
|
||||
|
||||
|
||||
it('should decrement 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});
|
||||
|
||||
var deregisterEvent2Listener = 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});
|
||||
|
||||
deregisterEvent2Listener();
|
||||
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 2});
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1});
|
||||
expect(child2.$$listenerCount).toEqual({});
|
||||
}))
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -1360,6 +1431,23 @@ describe('Scope', function() {
|
|||
}));
|
||||
|
||||
|
||||
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() {
|
||||
var result = child1.$broadcast('some');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue