Skip to content

Commit 49fa612

Browse files
committed
lib: lazily initialize kEvents and kHandlers maps
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
1 parent 6d02b36 commit 49fa612

3 files changed

Lines changed: 79 additions & 17 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const assert = require('node:assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e6],
8+
kind: ['AbortController', 'AbortSignal.timeout'],
9+
});
10+
11+
function main({ n, kind }) {
12+
let result;
13+
14+
switch (kind) {
15+
case 'AbortController':
16+
bench.start();
17+
for (let i = 0; i < n; i++)
18+
result = new AbortController().signal; // AbortSignal is created lazily on first .signal access
19+
bench.end(n);
20+
break;
21+
case 'AbortSignal.timeout':
22+
bench.start();
23+
for (let i = 0; i < n; i++)
24+
result = AbortSignal.timeout(1);
25+
bench.end(n);
26+
break;
27+
default:
28+
throw new Error('Invalid kind');
29+
}
30+
31+
// Avoid V8 dead code elimination
32+
assert.ok(result);
33+
}

lib/events.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ function getEventListeners(emitterOrTarget, type) {
927927
// Require event target lazily to avoid always loading it
928928
const { isEventTarget, kEvents } = require('internal/event_target');
929929
if (isEventTarget(emitterOrTarget)) {
930-
const root = emitterOrTarget[kEvents].get(type);
930+
const root = emitterOrTarget[kEvents]?.get(type);
931931
const listeners = [];
932932
let handler = root?.next;
933933
while (handler?.listener !== undefined) {
@@ -972,7 +972,7 @@ function listenerCount(emitterOrTarget, type) {
972972
}
973973
const { isEventTarget, kEvents } = require('internal/event_target');
974974
if (isEventTarget(emitterOrTarget)) {
975-
return emitterOrTarget[kEvents].get(type)?.size ?? 0;
975+
return emitterOrTarget[kEvents]?.get(type)?.size ?? 0;
976976
}
977977
throw new ERR_INVALID_ARG_TYPE('emitter',
978978
['EventEmitter', 'EventTarget'],

lib/internal/event_target.js

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -551,10 +551,16 @@ class Listener {
551551
}
552552

553553
function initEventTarget(self) {
554-
self[kEvents] = new SafeMap();
554+
self[kEvents] = undefined;
555555
self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners;
556556
self[kMaxEventTargetListenersWarned] = false;
557-
self[kHandlers] = new SafeMap();
557+
self[kHandlers] = undefined;
558+
}
559+
560+
function getEventsMap(self) {
561+
if (self[kEvents] === undefined)
562+
self[kEvents] = new SafeMap();
563+
return self[kEvents];
558564
}
559565

560566
class EventTarget {
@@ -660,7 +666,8 @@ class EventTarget {
660666

661667
type = webidl.converters.DOMString(type);
662668

663-
let root = this[kEvents].get(type);
669+
const eventsMap = getEventsMap(this);
670+
let root = eventsMap.get(type);
664671

665672
if (root === undefined) {
666673
root = { size: 1, next: undefined, resistStopPropagation: Boolean(resistStopPropagation) };
@@ -675,7 +682,7 @@ class EventTarget {
675682
capture,
676683
passive,
677684
weak);
678-
this[kEvents].set(type, root);
685+
eventsMap.set(type, root);
679686
return;
680687
}
681688

@@ -717,6 +724,9 @@ class EventTarget {
717724
type = webidl.converters.DOMString(type);
718725
const capture = options?.capture === true;
719726

727+
if (this[kEvents] === undefined)
728+
return;
729+
720730
const root = this[kEvents].get(type);
721731
if (root === undefined || root.next === undefined)
722732
return;
@@ -736,6 +746,9 @@ class EventTarget {
736746
}
737747

738748
[kRemoveWeakListenerHelper](type, listener) {
749+
if (this[kEvents] === undefined)
750+
return;
751+
739752
const root = this[kEvents].get(type);
740753
if (root === undefined || root.next === undefined)
741754
return;
@@ -791,7 +804,7 @@ class EventTarget {
791804
event[kIsBeingDispatched] = true;
792805
}
793806

794-
const root = this[kEvents].get(type);
807+
const root = this[kEvents]?.get(type);
795808
if (root === undefined || root.next === undefined) {
796809
if (event !== undefined)
797810
event[kIsBeingDispatched] = false;
@@ -927,6 +940,8 @@ class NodeEventTarget extends EventTarget {
927940
eventNames() {
928941
if (!isNodeEventTarget(this))
929942
throw new ERR_INVALID_THIS('NodeEventTarget');
943+
if (this[kEvents] === undefined)
944+
return [];
930945
return ArrayFrom(this[kEvents].keys());
931946
}
932947

@@ -937,6 +952,8 @@ class NodeEventTarget extends EventTarget {
937952
listenerCount(type) {
938953
if (!isNodeEventTarget(this))
939954
throw new ERR_INVALID_THIS('NodeEventTarget');
955+
if (this[kEvents] === undefined)
956+
return 0;
940957
const root = this[kEvents].get(String(type));
941958
return root !== undefined ? root.size : 0;
942959
}
@@ -1029,11 +1046,12 @@ class NodeEventTarget extends EventTarget {
10291046
removeAllListeners(type) {
10301047
if (!isNodeEventTarget(this))
10311048
throw new ERR_INVALID_THIS('NodeEventTarget');
1032-
if (type !== undefined) {
1049+
if (this[kEvents] === undefined)
1050+
return this;
1051+
if (type !== undefined)
10331052
this[kEvents].delete(String(type));
1034-
} else {
1053+
else
10351054
this[kEvents].clear();
1036-
}
10371055

10381056
return this;
10391057
}
@@ -1145,18 +1163,29 @@ function defineEventHandler(emitter, name, event = name) {
11451163

11461164
function set(value) {
11471165
validateThisInternalField(this, kHandlers, 'EventTarget');
1148-
let wrappedHandler = this[kHandlers]?.get(event);
1166+
if (this[kHandlers] === undefined)
1167+
this[kHandlers] = new SafeMap();
1168+
let wrappedHandler = this[kHandlers].get(event);
11491169
if (wrappedHandler) {
1170+
const eventsMap = getEventsMap(this);
11501171
if (typeof wrappedHandler.handler === 'function') {
1151-
this[kEvents].get(event).size--;
1152-
const size = this[kEvents].get(event).size;
1153-
this[kRemoveListener](size, event, wrappedHandler.handler, false);
1172+
const root = eventsMap.get(event);
1173+
if (root !== undefined) {
1174+
root.size--;
1175+
this[kRemoveListener](root.size, event, wrappedHandler.handler, false);
1176+
}
11541177
}
11551178
wrappedHandler.handler = value;
11561179
if (typeof wrappedHandler.handler === 'function') {
1157-
this[kEvents].get(event).size++;
1158-
const size = this[kEvents].get(event).size;
1159-
this[kNewListener](size, event, value, false, false, false, false);
1180+
const root = eventsMap.get(event);
1181+
if (root !== undefined) {
1182+
root.size++;
1183+
this[kNewListener](root.size, event, value, false, false, false, false);
1184+
} else {
1185+
// The wrapper was externally removed from kEvents (e.g. via
1186+
// removeEventListener); re-register it so the handler fires.
1187+
this.addEventListener(event, wrappedHandler);
1188+
}
11601189
}
11611190
} else {
11621191
wrappedHandler = makeEventHandler(value);

0 commit comments

Comments
 (0)