Skip to content

Commit 7061669

Browse files
mscdexbnoordhuis
authored andcommitted
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: #785 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Evan Lucas <evanlucas@me.com>
1 parent 630f636 commit 7061669

1 file changed

Lines changed: 69 additions & 45 deletions

File tree

‎lib/events.js‎

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ EventEmitter.init = function() {
3030
}
3131
}
3232

33-
if(!this._events||this._events===Object.getPrototypeOf(this)._events)
33+
if(!this._events||this._events===Object.getPrototypeOf(this)._events){
3434
this._events={};
35+
this._eventsCount=0;
36+
}
3537

3638
this._maxListeners=this._maxListeners||undefined;
3739
};
@@ -115,15 +117,18 @@ function emitMany(handler, isFn, self, args) {
115117
EventEmitter.prototype.emit=functionemit(type){
116118
varer,handler,len,args,i,events,domain;
117119
varneedDomainExit=false;
120+
vardoError=(type==='error');
118121

119122
events=this._events;
120-
if(!events)
121-
events=this._events={};
123+
if(events)
124+
doError=(doError&&events.error==null);
125+
elseif(!doError)
126+
returnfalse;
122127

123128
domain=this.domain;
124129

125130
// If there is no 'error' event listener then throw.
126-
if(type==='error'&&!events.error){
131+
if(doError){
127132
er=arguments[1];
128133
if(domain){
129134
if(!er)
@@ -189,39 +194,47 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
189194
thrownewTypeError('listener must be a function');
190195

191196
events=this._events;
192-
if(!events)
197+
if(!events){
193198
events=this._events={};
194-
else{
199+
this._eventsCount=0;
200+
}else{
195201
// To avoid recursion in the case that type === "newListener"! Before
196202
// adding it to the listeners, first emit "newListener".
197203
if(events.newListener){
198204
this.emit('newListener',type,
199-
typeoflistener.listener==='function' ?
200-
listener.listener : listener);
205+
listener.listener ? listener.listener : listener);
206+
207+
// Re-assign `events` because a newListener handler could have caused the
208+
// this._events to be assigned to a new object
209+
events=this._events;
201210
}
202211
existing=events[type];
203212
}
204213

205-
if(!existing)
214+
if(!existing){
206215
// Optimize the case of one listener. Don't need the extra array object.
207216
existing=events[type]=listener;
208-
elseif(typeofexisting!=='function')
209-
// If we've already got an array, just append.
210-
existing.push(listener);
211-
else
212-
// Adding the second element, need to change to array.
213-
existing=events[type]=[existing,listener];
214-
215-
// Check for listener leak
216-
if(typeofexisting!=='function'&&!existing.warned){
217-
m=$getMaxListeners(this);
218-
if(m&&m>0&&existing.length>m){
219-
existing.warned=true;
220-
console.error('(node) warning: possible EventEmitter memory '+
221-
'leak detected. %d %s listeners added. '+
222-
'Use emitter.setMaxListeners() to increase limit.',
223-
existing.length,type);
224-
console.trace();
217+
++this._eventsCount;
218+
}else{
219+
if(typeofexisting==='function'){
220+
// Adding the second element, need to change to array.
221+
existing=events[type]=[existing,listener];
222+
}else{
223+
// If we've already got an array, just append.
224+
existing.push(listener);
225+
}
226+
227+
// Check for listener leak
228+
if(!existing.warned){
229+
m=$getMaxListeners(this);
230+
if(m&&m>0&&existing.length>m){
231+
existing.warned=true;
232+
console.error('(node) warning: possible EventEmitter memory '+
233+
'leak detected. %d %s listeners added. '+
234+
'Use emitter.setMaxListeners() to increase limit.',
235+
existing.length,type);
236+
console.trace();
237+
}
225238
}
226239
}
227240

@@ -254,7 +267,7 @@ EventEmitter.prototype.once = function once(type, listener) {
254267
// emits a 'removeListener' event iff the listener was removed
255268
EventEmitter.prototype.removeListener=
256269
functionremoveListener(type,listener){
257-
varlist,events,position,length,i;
270+
varlist,events,position,i;
258271

259272
if(typeoflistener!=='function')
260273
thrownewTypeError('listener must be a function');
@@ -267,17 +280,18 @@ EventEmitter.prototype.removeListener =
267280
if(!list)
268281
returnthis;
269282

270-
length=list.length;
271-
position=-1;
272-
273-
if(list===listener||
274-
(typeoflist.listener==='function'&&list.listener===listener)){
275-
deleteevents[type];
276-
if(events.removeListener)
277-
this.emit('removeListener',type,listener);
278-
283+
if(list===listener||(list.listener&&list.listener===listener)){
284+
if(--this._eventsCount===0)
285+
this._events={};
286+
else{
287+
deleteevents[type];
288+
if(events.removeListener)
289+
this.emit('removeListener',type,listener);
290+
}
279291
}elseif(typeoflist!=='function'){
280-
for(i=length;i-->0;){
292+
position=-1;
293+
294+
for(i=list.length;i-->0;){
281295
if(list[i]===listener||
282296
(list[i].listener&&list[i].listener===listener)){
283297
position=i;
@@ -289,8 +303,12 @@ EventEmitter.prototype.removeListener =
289303
returnthis;
290304

291305
if(list.length===1){
292-
list.length=0;
293-
deleteevents[type];
306+
list[0]=undefined;
307+
if(--this._eventsCount===0){
308+
this._events={};
309+
returnthis;
310+
}else
311+
deleteevents[type];
294312
}else{
295313
spliceOne(list,position);
296314
}
@@ -312,10 +330,15 @@ EventEmitter.prototype.removeAllListeners =
312330

313331
// not listening for removeListener, no need to emit
314332
if(!events.removeListener){
315-
if(arguments.length===0)
333+
if(arguments.length===0){
316334
this._events={};
317-
elseif(events[type])
318-
deleteevents[type];
335+
this._eventsCount=0;
336+
}elseif(events[type]){
337+
if(--this._eventsCount===0)
338+
this._events={};
339+
else
340+
deleteevents[type];
341+
}
319342
returnthis;
320343
}
321344

@@ -329,19 +352,20 @@ EventEmitter.prototype.removeAllListeners =
329352
}
330353
this.removeAllListeners('removeListener');
331354
this._events={};
355+
this._eventsCount=0;
332356
returnthis;
333357
}
334358

335359
listeners=events[type];
336360

337361
if(typeoflisteners==='function'){
338362
this.removeListener(type,listeners);
339-
}elseif(Array.isArray(listeners)){
363+
}elseif(listeners){
340364
// LIFO order
341-
while(listeners.length)
365+
do{
342366
this.removeListener(type,listeners[listeners.length-1]);
367+
}while(listeners[0]);
343368
}
344-
deleteevents[type];
345369

346370
returnthis;
347371
};

0 commit comments

Comments
 (0)