mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-18 11:31:07 +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.$$asyncQueue = [];
|
||||||
this.$$postDigestQueue = [];
|
this.$$postDigestQueue = [];
|
||||||
this.$$listeners = {};
|
this.$$listeners = {};
|
||||||
|
this.$$listenerCount = {};
|
||||||
this.$$isolateBindings = {};
|
this.$$isolateBindings = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +193,7 @@ function $RootScopeProvider(){
|
||||||
}
|
}
|
||||||
child['this'] = child;
|
child['this'] = child;
|
||||||
child.$$listeners = {};
|
child.$$listeners = {};
|
||||||
|
child.$$listenerCount = {};
|
||||||
child.$parent = this;
|
child.$parent = this;
|
||||||
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
|
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
|
||||||
child.$$prevSibling = this.$$childTail;
|
child.$$prevSibling = this.$$childTail;
|
||||||
|
|
@ -696,6 +698,8 @@ function $RootScopeProvider(){
|
||||||
this.$$destroyed = true;
|
this.$$destroyed = true;
|
||||||
if (this === $rootScope) return;
|
if (this === $rootScope) return;
|
||||||
|
|
||||||
|
forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
|
||||||
|
|
||||||
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
|
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
|
||||||
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
|
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
|
||||||
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
|
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
|
||||||
|
|
@ -885,8 +889,18 @@ function $RootScopeProvider(){
|
||||||
}
|
}
|
||||||
namedListeners.push(listener);
|
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() {
|
return function() {
|
||||||
namedListeners[indexOf(namedListeners, listener)] = null;
|
namedListeners[indexOf(namedListeners, listener)] = null;
|
||||||
|
decrementListenerCount(self, 1, name);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -998,8 +1012,7 @@ function $RootScopeProvider(){
|
||||||
listeners, i, length;
|
listeners, i, length;
|
||||||
|
|
||||||
//down while you can, then up and next sibling or up and next sibling until back at root
|
//down while you can, then up and next sibling or up and next sibling until back at root
|
||||||
do {
|
while ((current = next)) {
|
||||||
current = next;
|
|
||||||
event.currentScope = current;
|
event.currentScope = current;
|
||||||
listeners = current.$$listeners[name] || [];
|
listeners = current.$$listeners[name] || [];
|
||||||
for (i=0, length = listeners.length; i<length; i++) {
|
for (i=0, length = listeners.length; i<length; i++) {
|
||||||
|
|
@ -1021,12 +1034,14 @@ function $RootScopeProvider(){
|
||||||
// Insanity Warning: scope depth-first traversal
|
// Insanity Warning: scope depth-first traversal
|
||||||
// yes, this code is a bit crazy, but it works and we have tests to prove it!
|
// 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
|
// 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)) {
|
while(current !== target && !(next = current.$$nextSibling)) {
|
||||||
current = current.$parent;
|
current = current.$parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while ((current = next));
|
}
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
@ -1055,6 +1070,16 @@ function $RootScopeProvider(){
|
||||||
return fn;
|
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.
|
* function used as an initial value for watchers.
|
||||||
* because it's unique we can easily tell it apart from other values
|
* because it's unique we can easily tell it apart from other values
|
||||||
|
|
|
||||||
|
|
@ -730,6 +730,28 @@ describe('Scope', function() {
|
||||||
first.$apply();
|
first.$apply();
|
||||||
expect(log).toBe('1232323');
|
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) {
|
it('should increment ancestor $$listenerCount entries', inject(function($rootScope) {
|
||||||
var log = '',
|
var child1 = $rootScope.$new(),
|
||||||
child = $rootScope.$new(),
|
child2 = child1.$new(),
|
||||||
listenerRemove;
|
spy = jasmine.createSpy();
|
||||||
|
|
||||||
function eventFn() {
|
$rootScope.$on('event1', spy);
|
||||||
log += 'X';
|
expect($rootScope.$$listenerCount).toEqual({event1: 1});
|
||||||
}
|
|
||||||
|
|
||||||
listenerRemove = child.$on('abc', eventFn);
|
child1.$on('event1', spy);
|
||||||
expect(log).toEqual('');
|
expect($rootScope.$$listenerCount).toEqual({event1: 2});
|
||||||
expect(listenerRemove).toBeDefined();
|
expect(child1.$$listenerCount).toEqual({event1: 1});
|
||||||
|
|
||||||
child.$emit('abc');
|
child2.$on('event2', spy);
|
||||||
child.$broadcast('abc');
|
expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1});
|
||||||
expect(log).toEqual('XX');
|
expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1});
|
||||||
|
expect(child2.$$listenerCount).toEqual({event2: 1});
|
||||||
log = '';
|
|
||||||
listenerRemove();
|
|
||||||
child.$emit('abc');
|
|
||||||
child.$broadcast('abc');
|
|
||||||
expect(log).toEqual('');
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
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() {
|
it('should return event object', function() {
|
||||||
var result = child1.$broadcast('some');
|
var result = child1.$broadcast('some');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue