diff --git a/benchmark/abort_controller/abort-controller-creation.js b/benchmark/abort_controller/abort-controller-creation.js new file mode 100644 index 00000000000000..cf21e2aac86585 --- /dev/null +++ b/benchmark/abort_controller/abort-controller-creation.js @@ -0,0 +1,33 @@ +'use strict'; + +const common = require('../common.js'); +const assert = require('node:assert'); + +const bench = common.createBenchmark(main, { + n: [1e6], + kind: ['AbortController', 'AbortSignal.timeout'], +}); + +function main({ n, kind }) { + let result; + + switch (kind) { + case 'AbortController': + bench.start(); + for (let i = 0; i < n; i++) + result = new AbortController().signal; // AbortSignal is created lazily on first .signal access + bench.end(n); + break; + case 'AbortSignal.timeout': + bench.start(); + for (let i = 0; i < n; i++) + result = AbortSignal.timeout(1); + bench.end(n); + break; + default: + throw new Error('Invalid kind'); + } + + // Avoid V8 dead code elimination + assert.ok(result); +} diff --git a/lib/events.js b/lib/events.js index 7044423692e1bf..e385e8dddc4274 100644 --- a/lib/events.js +++ b/lib/events.js @@ -927,7 +927,7 @@ function getEventListeners(emitterOrTarget, type) { // Require event target lazily to avoid always loading it const { isEventTarget, kEvents } = require('internal/event_target'); if (isEventTarget(emitterOrTarget)) { - const root = emitterOrTarget[kEvents].get(type); + const root = emitterOrTarget[kEvents]?.get(type); const listeners = []; let handler = root?.next; while (handler?.listener !== undefined) { @@ -972,7 +972,7 @@ function listenerCount(emitterOrTarget, type) { } const { isEventTarget, kEvents } = require('internal/event_target'); if (isEventTarget(emitterOrTarget)) { - return emitterOrTarget[kEvents].get(type)?.size ?? 0; + return emitterOrTarget[kEvents]?.get(type)?.size ?? 0; } throw new ERR_INVALID_ARG_TYPE('emitter', ['EventEmitter', 'EventTarget'], diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index 9948d3f1868e32..5a4f7c02909464 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -551,10 +551,16 @@ class Listener { } function initEventTarget(self) { - self[kEvents] = new SafeMap(); + self[kEvents] = undefined; self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners; self[kMaxEventTargetListenersWarned] = false; - self[kHandlers] = new SafeMap(); + self[kHandlers] = undefined; +} + +function getEventsMap(self) { + if (self[kEvents] === undefined) + self[kEvents] = new SafeMap(); + return self[kEvents]; } class EventTarget { @@ -660,7 +666,8 @@ class EventTarget { type = webidl.converters.DOMString(type); - let root = this[kEvents].get(type); + const eventsMap = getEventsMap(this); + let root = eventsMap.get(type); if (root === undefined) { root = { size: 1, next: undefined, resistStopPropagation: Boolean(resistStopPropagation) }; @@ -675,7 +682,7 @@ class EventTarget { capture, passive, weak); - this[kEvents].set(type, root); + eventsMap.set(type, root); return; } @@ -717,6 +724,9 @@ class EventTarget { type = webidl.converters.DOMString(type); const capture = options?.capture === true; + if (this[kEvents] === undefined) + return; + const root = this[kEvents].get(type); if (root === undefined || root.next === undefined) return; @@ -736,6 +746,9 @@ class EventTarget { } [kRemoveWeakListenerHelper](type, listener) { + if (this[kEvents] === undefined) + return; + const root = this[kEvents].get(type); if (root === undefined || root.next === undefined) return; @@ -791,7 +804,7 @@ class EventTarget { event[kIsBeingDispatched] = true; } - const root = this[kEvents].get(type); + const root = this[kEvents]?.get(type); if (root === undefined || root.next === undefined) { if (event !== undefined) event[kIsBeingDispatched] = false; @@ -927,6 +940,8 @@ class NodeEventTarget extends EventTarget { eventNames() { if (!isNodeEventTarget(this)) throw new ERR_INVALID_THIS('NodeEventTarget'); + if (this[kEvents] === undefined) + return []; return ArrayFrom(this[kEvents].keys()); } @@ -937,6 +952,8 @@ class NodeEventTarget extends EventTarget { listenerCount(type) { if (!isNodeEventTarget(this)) throw new ERR_INVALID_THIS('NodeEventTarget'); + if (this[kEvents] === undefined) + return 0; const root = this[kEvents].get(String(type)); return root !== undefined ? root.size : 0; } @@ -1029,11 +1046,12 @@ class NodeEventTarget extends EventTarget { removeAllListeners(type) { if (!isNodeEventTarget(this)) throw new ERR_INVALID_THIS('NodeEventTarget'); - if (type !== undefined) { + if (this[kEvents] === undefined) + return this; + if (type !== undefined) this[kEvents].delete(String(type)); - } else { + else this[kEvents].clear(); - } return this; } @@ -1145,18 +1163,29 @@ function defineEventHandler(emitter, name, event = name) { function set(value) { validateThisInternalField(this, kHandlers, 'EventTarget'); - let wrappedHandler = this[kHandlers]?.get(event); + if (this[kHandlers] === undefined) + this[kHandlers] = new SafeMap(); + let wrappedHandler = this[kHandlers].get(event); if (wrappedHandler) { + const eventsMap = getEventsMap(this); if (typeof wrappedHandler.handler === 'function') { - this[kEvents].get(event).size--; - const size = this[kEvents].get(event).size; - this[kRemoveListener](size, event, wrappedHandler.handler, false); + const root = eventsMap.get(event); + if (root !== undefined) { + root.size--; + this[kRemoveListener](root.size, event, wrappedHandler.handler, false); + } } wrappedHandler.handler = value; if (typeof wrappedHandler.handler === 'function') { - this[kEvents].get(event).size++; - const size = this[kEvents].get(event).size; - this[kNewListener](size, event, value, false, false, false, false); + const root = eventsMap.get(event); + if (root !== undefined) { + root.size++; + this[kNewListener](root.size, event, value, false, false, false, false); + } else { + // The wrapper was externally removed from kEvents (e.g. via + // removeEventListener); re-register it so the handler fires. + this.addEventListener(event, wrappedHandler); + } } } else { wrappedHandler = makeEventHandler(value);