Skip to content

Commit d84a953

Browse files
committed
events: use a Map to store listeners
1 parent 6cd0e26 commit d84a953

11 files changed

+100
-92
lines changed

lib/_stream_readable.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module.exports = Readable;
44
Readable.ReadableState = ReadableState;
55

66
const EE = require('events').EventEmitter;
7+
const EEEvents = require('internal/symbols').EEEvents;
78
const Stream = require('stream');
89
const Buffer = require('buffer').Buffer;
910
const util = require('util');
@@ -542,12 +543,13 @@ Readable.prototype.pipe = function(dest, pipeOpts) {
542543
}
543544
// This is a brutally ugly hack to make sure that our error handler
544545
// is attached before any userland ones. NEVER DO THIS.
545-
if (!dest._events || !dest._events.error)
546+
const events = dest[EEEvents];
547+
if (!events || !events.has('error'))
546548
dest.on('error', onerror);
547-
else if (Array.isArray(dest._events.error))
548-
dest._events.error.unshift(onerror);
549+
else if (Array.isArray(events.get('error')))
550+
events.get('error').unshift(onerror);
549551
else
550-
dest._events.error = [onerror, dest._events.error];
552+
events.set('error', [onerror, events.get('error')]);
551553

552554

553555
// Both close and finish should trigger unpipe, but only once.

lib/events.js

Lines changed: 52 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const EEEvents = require('internal/symbols').EEEvents;
4+
35
var domain;
46

57
function EventEmitter() {
@@ -13,7 +15,7 @@ EventEmitter.EventEmitter = EventEmitter;
1315
EventEmitter.usingDomains = false;
1416

1517
EventEmitter.prototype.domain = undefined;
16-
EventEmitter.prototype._events = undefined;
18+
EventEmitter.prototype[EEEvents] = undefined;
1719
EventEmitter.prototype._maxListeners = undefined;
1820

1921
// By default EventEmitters will print a warning if more than 10 listeners are
@@ -30,10 +32,9 @@ EventEmitter.init = function() {
3032
}
3133
}
3234

33-
if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
34-
this._events = {};
35-
this._eventsCount = 0;
36-
}
35+
if (!this[EEEvents] ||
36+
this[EEEvents] === Object.getPrototypeOf(this)[EEEvents])
37+
this[EEEvents] = new Map();
3738

3839
this._maxListeners = this._maxListeners || undefined;
3940
};
@@ -119,9 +120,9 @@ EventEmitter.prototype.emit = function emit(type) {
119120
var needDomainExit = false;
120121
var doError = (type === 'error');
121122

122-
events = this._events;
123+
events = this[EEEvents];
123124
if (events)
124-
doError = (doError && events.error == null);
125+
doError = (doError && !events.has('error'));
125126
else if (!doError)
126127
return false;
127128

@@ -148,7 +149,7 @@ EventEmitter.prototype.emit = function emit(type) {
148149
return false;
149150
}
150151

151-
handler = events[type];
152+
handler = events.get(type);
152153

153154
if (!handler)
154155
return false;
@@ -196,32 +197,31 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
196197
if (typeof listener !== 'function')
197198
throw new TypeError('listener must be a function');
198199

199-
events = this._events;
200+
events = this[EEEvents];
200201
if (!events) {
201-
events = this._events = {};
202-
this._eventsCount = 0;
202+
events = this[EEEvents] = new Map();
203203
} else {
204204
// To avoid recursion in the case that type === "newListener"! Before
205205
// adding it to the listeners, first emit "newListener".
206-
if (events.newListener) {
206+
if (events.has('newListener')) {
207207
this.emit('newListener', type,
208208
listener.listener ? listener.listener : listener);
209209

210210
// Re-assign `events` because a newListener handler could have caused the
211-
// this._events to be assigned to a new object
212-
events = this._events;
211+
// this[EEEvents] to be assigned to a new object
212+
events = this[EEEvents];
213213
}
214-
existing = events[type];
214+
existing = events.get(type);
215215
}
216216

217217
if (!existing) {
218218
// Optimize the case of one listener. Don't need the extra array object.
219-
existing = events[type] = listener;
220-
++this._eventsCount;
219+
events.set(type, listener);
221220
} else {
222221
if (typeof existing === 'function') {
223222
// Adding the second element, need to change to array.
224-
existing = events[type] = [existing, listener];
223+
existing = [existing, listener];
224+
events.set(type, existing);
225225
} else {
226226
// If we've already got an array, just append.
227227
existing.push(listener);
@@ -267,30 +267,26 @@ EventEmitter.prototype.once = function once(type, listener) {
267267
return this;
268268
};
269269

270-
// emits a 'removeListener' event iff the listener was removed
270+
// emits a 'removeListener' event if the listener was removed
271271
EventEmitter.prototype.removeListener =
272272
function removeListener(type, listener) {
273273
var list, events, position, i;
274274

275275
if (typeof listener !== 'function')
276276
throw new TypeError('listener must be a function');
277277

278-
events = this._events;
278+
events = this[EEEvents];
279279
if (!events)
280280
return this;
281281

282-
list = events[type];
282+
list = events.get(type);
283283
if (!list)
284284
return this;
285285

286286
if (list === listener || (list.listener && list.listener === listener)) {
287-
if (--this._eventsCount === 0)
288-
this._events = {};
289-
else {
290-
delete events[type];
291-
if (events.removeListener)
292-
this.emit('removeListener', type, listener);
293-
}
287+
events.delete(type);
288+
if (events.has('removeListener'))
289+
this.emit('removeListener', type, listener);
294290
} else if (typeof list !== 'function') {
295291
position = -1;
296292

@@ -307,17 +303,12 @@ EventEmitter.prototype.removeListener =
307303

308304
if (list.length === 1) {
309305
list[0] = undefined;
310-
if (--this._eventsCount === 0) {
311-
this._events = {};
312-
return this;
313-
} else {
314-
delete events[type];
315-
}
306+
events.delete(type);
316307
} else {
317308
spliceOne(list, position);
318309
}
319310

320-
if (events.removeListener)
311+
if (events.has('removeListener'))
321312
this.emit('removeListener', type, listener);
322313
}
323314

@@ -328,39 +319,32 @@ EventEmitter.prototype.removeAllListeners =
328319
function removeAllListeners(type) {
329320
var listeners, events;
330321

331-
events = this._events;
322+
events = this[EEEvents];
332323
if (!events)
333324
return this;
334325

335326
// not listening for removeListener, no need to emit
336-
if (!events.removeListener) {
327+
if (!events.has('removeListener')) {
337328
if (arguments.length === 0) {
338-
this._events = {};
339-
this._eventsCount = 0;
340-
} else if (events[type]) {
341-
if (--this._eventsCount === 0)
342-
this._events = {};
343-
else
344-
delete events[type];
329+
events.clear();
330+
} else {
331+
events.delete(type);
345332
}
346333
return this;
347334
}
348335

349336
// emit removeListener for all listeners on all events
350337
if (arguments.length === 0) {
351-
var keys = Object.keys(events);
352-
for (var i = 0, key; i < keys.length; ++i) {
353-
key = keys[i];
338+
for (var key of events.keys()) {
354339
if (key === 'removeListener') continue;
355340
this.removeAllListeners(key);
356341
}
357342
this.removeAllListeners('removeListener');
358-
this._events = {};
359-
this._eventsCount = 0;
343+
events.clear();
360344
return this;
361345
}
362346

363-
listeners = events[type];
347+
listeners = events.get(type);
364348

365349
if (typeof listeners === 'function') {
366350
this.removeListener(type, listeners);
@@ -377,12 +361,12 @@ EventEmitter.prototype.removeAllListeners =
377361
EventEmitter.prototype.listeners = function listeners(type) {
378362
var evlistener;
379363
var ret;
380-
var events = this._events;
364+
var events = this[EEEvents];
381365

382366
if (!events)
383367
ret = [];
384368
else {
385-
evlistener = events[type];
369+
evlistener = events.get(type);
386370
if (!evlistener)
387371
ret = [];
388372
else if (typeof evlistener === 'function')
@@ -399,10 +383,10 @@ EventEmitter.listenerCount = function(emitter, type) {
399383
};
400384

401385
EventEmitter.prototype.listenerCount = function listenerCount(type) {
402-
const events = this._events;
386+
const events = this[EEEvents];
403387

404388
if (events) {
405-
const evlistener = events[type];
389+
const evlistener = events.get(type);
406390

407391
if (typeof evlistener === 'function') {
408392
return 1;
@@ -414,6 +398,20 @@ EventEmitter.prototype.listenerCount = function listenerCount(type) {
414398
return 0;
415399
};
416400

401+
Object.defineProperty(EventEmitter.prototype, '_events', {
402+
get() {
403+
const thisEvents = this[EEEvents];
404+
if (!thisEvents)
405+
return;
406+
407+
const events = {};
408+
for (var event of thisEvents) {
409+
events[event[0]] = event[1];
410+
}
411+
return events;
412+
}
413+
});
414+
417415
// About 1.5x faster than the two-arg version of Array#splice().
418416
function spliceOne(list, index) {
419417
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)

lib/internal/symbols.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
exports.EEEvents = Symbol('EventEmitter events');

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
'lib/internal/child_process.js',
7272
'lib/internal/freelist.js',
7373
'lib/internal/socket_list.js',
74+
'lib/internal/symbols.js',
7475
'lib/internal/repl.js',
7576
'lib/internal/util.js',
7677
],

src/env.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ namespace node {
7878
V(env_string, "env") \
7979
V(errno_string, "errno") \
8080
V(error_string, "error") \
81-
V(events_string, "_events") \
8281
V(exec_argv_string, "execArgv") \
8382
V(exec_path_string, "execPath") \
8483
V(exiting_string, "_exiting") \

src/node.cc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2925,9 +2925,6 @@ void SetupProcessObject(Environment* env,
29252925
env->SetMethod(process, "_setupNextTick", SetupNextTick);
29262926
env->SetMethod(process, "_setupPromises", SetupPromises);
29272927
env->SetMethod(process, "_setupDomainUse", SetupDomainUse);
2928-
2929-
// pre-set _events object for faster emit checks
2930-
process->Set(env->events_string(), Object::New(env->isolate()));
29312928
}
29322929

29332930

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
11
'use strict';
2+
// Flags: --expose-internals
23
var common = require('../common');
34
var assert = require('assert');
45
var events = require('events');
6+
const EEEvents = require('internal/symbols').EEEvents;
57

68
var e = new events.EventEmitter();
79

810
// default
911
for (var i = 0; i < 10; i++) {
1012
e.on('default', function() {});
1113
}
12-
assert.ok(!e._events['default'].hasOwnProperty('warned'));
14+
assert.ok(!e[EEEvents].get('default').hasOwnProperty('warned'));
1315
e.on('default', function() {});
14-
assert.ok(e._events['default'].warned);
16+
assert.ok(e[EEEvents].get('default').warned);
1517

1618
// specific
1719
e.setMaxListeners(5);
1820
for (var i = 0; i < 5; i++) {
1921
e.on('specific', function() {});
2022
}
21-
assert.ok(!e._events['specific'].hasOwnProperty('warned'));
23+
assert.ok(!e[EEEvents].get('specific').hasOwnProperty('warned'));
2224
e.on('specific', function() {});
23-
assert.ok(e._events['specific'].warned);
25+
assert.ok(e[EEEvents].get('specific').warned);
2426

2527
// only one
2628
e.setMaxListeners(1);
2729
e.on('only one', function() {});
28-
assert.ok(!e._events['only one'].hasOwnProperty('warned'));
30+
assert.ok(!e[EEEvents].get('only one').hasOwnProperty('warned'));
2931
e.on('only one', function() {});
30-
assert.ok(e._events['only one'].hasOwnProperty('warned'));
32+
assert.ok(e[EEEvents].get('only one').hasOwnProperty('warned'));
3133

3234
// unlimited
3335
e.setMaxListeners(0);
3436
for (var i = 0; i < 1000; i++) {
3537
e.on('unlimited', function() {});
3638
}
37-
assert.ok(!e._events['unlimited'].hasOwnProperty('warned'));
39+
assert.ok(!e[EEEvents].get('unlimited').hasOwnProperty('warned'));
3840

3941
// process-wide
4042
events.EventEmitter.defaultMaxListeners = 42;
@@ -43,25 +45,25 @@ e = new events.EventEmitter();
4345
for (var i = 0; i < 42; ++i) {
4446
e.on('fortytwo', function() {});
4547
}
46-
assert.ok(!e._events['fortytwo'].hasOwnProperty('warned'));
48+
assert.ok(!e[EEEvents].get('fortytwo').hasOwnProperty('warned'));
4749
e.on('fortytwo', function() {});
48-
assert.ok(e._events['fortytwo'].hasOwnProperty('warned'));
49-
delete e._events['fortytwo'].warned;
50+
assert.ok(e[EEEvents].get('fortytwo').hasOwnProperty('warned'));
51+
delete e[EEEvents].get('fortytwo').warned;
5052

5153
events.EventEmitter.defaultMaxListeners = 44;
5254
e.on('fortytwo', function() {});
53-
assert.ok(!e._events['fortytwo'].hasOwnProperty('warned'));
55+
assert.ok(!e[EEEvents].get('fortytwo').hasOwnProperty('warned'));
5456
e.on('fortytwo', function() {});
55-
assert.ok(e._events['fortytwo'].hasOwnProperty('warned'));
57+
assert.ok(e[EEEvents].get('fortytwo').hasOwnProperty('warned'));
5658

5759
// but _maxListeners still has precedence over defaultMaxListeners
5860
events.EventEmitter.defaultMaxListeners = 42;
5961
e = new events.EventEmitter();
6062
e.setMaxListeners(1);
6163
e.on('uno', function() {});
62-
assert.ok(!e._events['uno'].hasOwnProperty('warned'));
64+
assert.ok(!e[EEEvents].get('uno').hasOwnProperty('warned'));
6365
e.on('uno', function() {});
64-
assert.ok(e._events['uno'].hasOwnProperty('warned'));
66+
assert.ok(e[EEEvents].get('uno').hasOwnProperty('warned'));
6567

6668
// chainable
6769
assert.strictEqual(e, e.setMaxListeners(1));

0 commit comments

Comments
 (0)