events: optimize adding and removing of listeners
These optimizations result in >2x speedup in the ee-add-remove benchmark: * Don't mutate array.length when removing the last listener for an event * Don't bother checking max listeners if listeners isn't an array * Don't call delete when removing the last event in _events, just re-assign a new object instead PR-URL: https://github.com/iojs/io.js/pull/785 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Evan Lucas <evanlucas@me.com>
This commit is contained in:
parent
630f636334
commit
7061669dba
@ -30,8 +30,10 @@ EventEmitter.init = function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._events || this._events === Object.getPrototypeOf(this)._events)
|
if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
|
||||||
this._events = {};
|
this._events = {};
|
||||||
|
this._eventsCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this._maxListeners = this._maxListeners || undefined;
|
this._maxListeners = this._maxListeners || undefined;
|
||||||
};
|
};
|
||||||
@ -115,15 +117,18 @@ function emitMany(handler, isFn, self, args) {
|
|||||||
EventEmitter.prototype.emit = function emit(type) {
|
EventEmitter.prototype.emit = function emit(type) {
|
||||||
var er, handler, len, args, i, events, domain;
|
var er, handler, len, args, i, events, domain;
|
||||||
var needDomainExit = false;
|
var needDomainExit = false;
|
||||||
|
var doError = (type === 'error');
|
||||||
|
|
||||||
events = this._events;
|
events = this._events;
|
||||||
if (!events)
|
if (events)
|
||||||
events = this._events = {};
|
doError = (doError && events.error == null);
|
||||||
|
else if (!doError)
|
||||||
|
return false;
|
||||||
|
|
||||||
domain = this.domain;
|
domain = this.domain;
|
||||||
|
|
||||||
// If there is no 'error' event listener then throw.
|
// If there is no 'error' event listener then throw.
|
||||||
if (type === 'error' && !events.error) {
|
if (doError) {
|
||||||
er = arguments[1];
|
er = arguments[1];
|
||||||
if (domain) {
|
if (domain) {
|
||||||
if (!er)
|
if (!er)
|
||||||
@ -189,31 +194,38 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
|
|||||||
throw new TypeError('listener must be a function');
|
throw new TypeError('listener must be a function');
|
||||||
|
|
||||||
events = this._events;
|
events = this._events;
|
||||||
if (!events)
|
if (!events) {
|
||||||
events = this._events = {};
|
events = this._events = {};
|
||||||
else {
|
this._eventsCount = 0;
|
||||||
|
} else {
|
||||||
// To avoid recursion in the case that type === "newListener"! Before
|
// To avoid recursion in the case that type === "newListener"! Before
|
||||||
// adding it to the listeners, first emit "newListener".
|
// adding it to the listeners, first emit "newListener".
|
||||||
if (events.newListener) {
|
if (events.newListener) {
|
||||||
this.emit('newListener', type,
|
this.emit('newListener', type,
|
||||||
typeof listener.listener === 'function' ?
|
listener.listener ? listener.listener : listener);
|
||||||
listener.listener : listener);
|
|
||||||
|
// Re-assign `events` because a newListener handler could have caused the
|
||||||
|
// this._events to be assigned to a new object
|
||||||
|
events = this._events;
|
||||||
}
|
}
|
||||||
existing = events[type];
|
existing = events[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existing)
|
if (!existing) {
|
||||||
// Optimize the case of one listener. Don't need the extra array object.
|
// Optimize the case of one listener. Don't need the extra array object.
|
||||||
existing = events[type] = listener;
|
existing = events[type] = listener;
|
||||||
else if (typeof existing !== 'function')
|
++this._eventsCount;
|
||||||
// If we've already got an array, just append.
|
} else {
|
||||||
existing.push(listener);
|
if (typeof existing === 'function') {
|
||||||
else
|
|
||||||
// Adding the second element, need to change to array.
|
// Adding the second element, need to change to array.
|
||||||
existing = events[type] = [existing, listener];
|
existing = events[type] = [existing, listener];
|
||||||
|
} else {
|
||||||
|
// If we've already got an array, just append.
|
||||||
|
existing.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for listener leak
|
// Check for listener leak
|
||||||
if (typeof existing !== 'function' && !existing.warned) {
|
if (!existing.warned) {
|
||||||
m = $getMaxListeners(this);
|
m = $getMaxListeners(this);
|
||||||
if (m && m > 0 && existing.length > m) {
|
if (m && m > 0 && existing.length > m) {
|
||||||
existing.warned = true;
|
existing.warned = true;
|
||||||
@ -224,6 +236,7 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
|
|||||||
console.trace();
|
console.trace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@ -254,7 +267,7 @@ EventEmitter.prototype.once = function once(type, listener) {
|
|||||||
// emits a 'removeListener' event iff the listener was removed
|
// emits a 'removeListener' event iff the listener was removed
|
||||||
EventEmitter.prototype.removeListener =
|
EventEmitter.prototype.removeListener =
|
||||||
function removeListener(type, listener) {
|
function removeListener(type, listener) {
|
||||||
var list, events, position, length, i;
|
var list, events, position, i;
|
||||||
|
|
||||||
if (typeof listener !== 'function')
|
if (typeof listener !== 'function')
|
||||||
throw new TypeError('listener must be a function');
|
throw new TypeError('listener must be a function');
|
||||||
@ -267,17 +280,18 @@ EventEmitter.prototype.removeListener =
|
|||||||
if (!list)
|
if (!list)
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
length = list.length;
|
if (list === listener || (list.listener && list.listener === listener)) {
|
||||||
position = -1;
|
if (--this._eventsCount === 0)
|
||||||
|
this._events = {};
|
||||||
if (list === listener ||
|
else {
|
||||||
(typeof list.listener === 'function' && list.listener === listener)) {
|
|
||||||
delete events[type];
|
delete events[type];
|
||||||
if (events.removeListener)
|
if (events.removeListener)
|
||||||
this.emit('removeListener', type, listener);
|
this.emit('removeListener', type, listener);
|
||||||
|
}
|
||||||
} else if (typeof list !== 'function') {
|
} else if (typeof list !== 'function') {
|
||||||
for (i = length; i-- > 0;) {
|
position = -1;
|
||||||
|
|
||||||
|
for (i = list.length; i-- > 0;) {
|
||||||
if (list[i] === listener ||
|
if (list[i] === listener ||
|
||||||
(list[i].listener && list[i].listener === listener)) {
|
(list[i].listener && list[i].listener === listener)) {
|
||||||
position = i;
|
position = i;
|
||||||
@ -289,7 +303,11 @@ EventEmitter.prototype.removeListener =
|
|||||||
return this;
|
return this;
|
||||||
|
|
||||||
if (list.length === 1) {
|
if (list.length === 1) {
|
||||||
list.length = 0;
|
list[0] = undefined;
|
||||||
|
if (--this._eventsCount === 0) {
|
||||||
|
this._events = {};
|
||||||
|
return this;
|
||||||
|
} else
|
||||||
delete events[type];
|
delete events[type];
|
||||||
} else {
|
} else {
|
||||||
spliceOne(list, position);
|
spliceOne(list, position);
|
||||||
@ -312,10 +330,15 @@ EventEmitter.prototype.removeAllListeners =
|
|||||||
|
|
||||||
// not listening for removeListener, no need to emit
|
// not listening for removeListener, no need to emit
|
||||||
if (!events.removeListener) {
|
if (!events.removeListener) {
|
||||||
if (arguments.length === 0)
|
if (arguments.length === 0) {
|
||||||
this._events = {};
|
this._events = {};
|
||||||
else if (events[type])
|
this._eventsCount = 0;
|
||||||
|
} else if (events[type]) {
|
||||||
|
if (--this._eventsCount === 0)
|
||||||
|
this._events = {};
|
||||||
|
else
|
||||||
delete events[type];
|
delete events[type];
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,6 +352,7 @@ EventEmitter.prototype.removeAllListeners =
|
|||||||
}
|
}
|
||||||
this.removeAllListeners('removeListener');
|
this.removeAllListeners('removeListener');
|
||||||
this._events = {};
|
this._events = {};
|
||||||
|
this._eventsCount = 0;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,12 +360,12 @@ EventEmitter.prototype.removeAllListeners =
|
|||||||
|
|
||||||
if (typeof listeners === 'function') {
|
if (typeof listeners === 'function') {
|
||||||
this.removeListener(type, listeners);
|
this.removeListener(type, listeners);
|
||||||
} else if (Array.isArray(listeners)) {
|
} else if (listeners) {
|
||||||
// LIFO order
|
// LIFO order
|
||||||
while (listeners.length)
|
do {
|
||||||
this.removeListener(type, listeners[listeners.length - 1]);
|
this.removeListener(type, listeners[listeners.length - 1]);
|
||||||
|
} while (listeners[0]);
|
||||||
}
|
}
|
||||||
delete events[type];
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user