diff --git a/lib/events.js b/lib/events.js index 5719d948c739d1..8b20ea994d6cee 100644 --- a/lib/events.js +++ b/lib/events.js @@ -86,6 +86,7 @@ const { addAbortListener } = require('internal/events/abort_listener'); const kCapture = Symbol('kCapture'); const kErrorMonitor = Symbol('events.errorMonitor'); +const kShapeMode = Symbol('shapeMode'); const kEmitting = Symbol('events.emitting'); const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); const kMaxEventTargetListenersWarned = @@ -334,6 +335,9 @@ EventEmitter.init = function(opts) { this._events === ObjectGetPrototypeOf(this)._events) { this._events = { __proto__: null }; this._eventsCount = 0; + this[kShapeMode] = false; + } else { + this[kShapeMode] = true; } this._maxListeners ||= undefined; @@ -687,12 +691,13 @@ EventEmitter.prototype.removeListener = if (list === listener || list.listener === listener) { this._eventsCount -= 1; - // Leave the key in place with an `undefined` value: repeatedly - // adding and removing a listener for the same event this way keeps - // the `events` object in the same shape and avoids both a `delete` - // (which would put the object into dictionary mode) and allocating - // a fresh object when the last listener is removed. - events[type] = undefined; + if (this[kShapeMode]) { + events[type] = undefined; + } else if (this._eventsCount === 0) { + this._events = { __proto__: null }; + } else { + delete events[type]; + } if (events.removeListener !== undefined) this.emit('removeListener', type, list.listener || listener); @@ -752,6 +757,7 @@ EventEmitter.prototype.removeAllListeners = else delete events[type]; } + this[kShapeMode] = false; return this; } @@ -764,6 +770,7 @@ EventEmitter.prototype.removeAllListeners = this.removeAllListeners('removeListener'); this._events = { __proto__: null }; this._eventsCount = 0; + this[kShapeMode] = false; return this; } diff --git a/test/parallel/test-event-emitter-remove-listeners.js b/test/parallel/test-event-emitter-remove-listeners.js index 7b94d31f45d221..e9e4a74a5b633e 100644 --- a/test/parallel/test-event-emitter-remove-listeners.js +++ b/test/parallel/test-event-emitter-remove-listeners.js @@ -144,6 +144,19 @@ function listener2() {} assert.deepStrictEqual(ee, ee.removeListener('foo', () => {})); } +{ + const ee = new EventEmitter(); + ee.on('persistent', listener1); + + for (let i = 0; i < 100; i++) { + const eventName = `event-${i}`; + ee.on(eventName, listener2); + ee.removeListener(eventName, listener2); + } + + assert.deepStrictEqual(Reflect.ownKeys(ee._events), ['persistent']); +} + { const ee = new EventEmitter(); const listener = () => {};