on master help sign in
Use the same function for all DOM event handlers

This changes the event handler setup to reuse a single function for all DOM event handling. Instead of creating a new listener for each event, the same function is registered as a handler. It looks up properties on the `this` object to use as values for `a` and `b` passed into the trampoline. This gets rid of all or almost all GC activity in a simple handler-heavy test case I looked at. AFAICT it should be entirely neutral regarding functionality.

commit49fdb9360cc634d01237dd5261c70c8d961d9282 authorTill Schneidereit <till@tillschneidereit.net> dateTue 12 Mar 2019, 12:16 (8 years ago) parent7134c89
1 file changed+23−38
js/change-list.js+23−38
js/change-list.js+23−38
@@ -135,10 +135,9 @@ const OP_TABLE = [
135135 const a = mem32[i++];
136136 const b = mem32[i++];
137137 const el = top(changeList.stack);
138− const listener = new Listener(a, b, eventType, changeList.eventsTrampoline, el);
139− changeList.listeners.add(listener);
140− el.addEventListener(eventType, listener.callback);
141− el[`dodrio-${eventType}`] = listener;
138+ el.addEventListener(eventType, changeList.eventHandler);
139+ el["dodrio-a-" + eventType] = a;
140+ el["dodrio-b-" + eventType] = b;
142141 return i;
143142 },
144143
@@ -147,9 +146,8 @@ const OP_TABLE = [
147146 const eventId = mem32[i++];
148147 const eventType = changeList.getString(eventId);
149148 const el = top(changeList.stack);
150− const listener = el[`dodrio-${eventType}`];
151− listener.a = mem32[i++];
152− listener.b = mem32[i++];
149+ el["dodrio-a-" + eventType] = mem32[i++];
150+ el["dodrio-b-" + eventType] = mem32[i++];
153151 return i;
154152 },
155153
@@ -158,9 +156,7 @@ const OP_TABLE = [
158156 const eventId = mem32[i++];
159157 const eventType = changeList.getString(eventId);
160158 const el = top(changeList.stack);
161− const listener = el[`dodrio-${eventType}`];
162− el.removeEventListener(eventType, listener.callback);
163− changeList.listeners.delete(listener);
159+ el.removeEventListener(eventType, changeList.eventHandler);
164160 return i;
165161 },
166162
@@ -182,25 +178,9 @@ const OP_TABLE = [
182178 }
183179];
184180
185−class Listener {
186− constructor(a, b, eventType, trampoline, el) {
187− this.a = a;
188− this.b = b;
189− this.eventType = eventType;
190− this.trampoline = trampoline;
191− this.el = el;
192− this.callback = this.callback.bind(this);
193− }
194−
195− callback(event) {
196− this.trampoline(event, this.a, this.b);
197− }
198−}
199−
200181// export
201182class ChangeList {
202183 constructor(container) {
203− this.listeners = new Set();
204184 this.trampoline = null;
205185 this.container = container;
206186 this.ranges = [];
@@ -209,18 +189,10 @@ class ChangeList {
209189 }
210190
211191 unmount() {
212− for (const listener of this.listeners) {
213− listener.el.removeEventListener(listener.eventType, listener.callback);
214− listener.trampoline = () => {
215− throw new Error("invocation of listener after it has been removed");
216− };
217− listener.a = 0;
218− listener.b = 0;
219− }
192+ this.trampoline.mounted = false;
220193
221194 // Null out all of our properties just to ensure that if we mistakenly ever
222195 // call a method on this instance again, it will throw.
223− this.listeners = null;
224196 this.trampoline = null;
225197 this.container = null;
226198 this.ranges = null;
@@ -273,9 +245,22 @@ class ChangeList {
273245 }
274246
275247 initEventsTrampoline(trampoline) {
276− this.eventsTrampoline = (...args) => {
277− trampoline(...args);
278− };
248+ this.trampoline = trampoline;
249+ trampoline.mounted = true;
250+ this.eventHandler = function(event) {
251+ if (!trampoline.mounted) {
252+ throw new Error("invocation of listener after VDOM has been unmounted");
253+ }
254+
255+ // `this` always refers to the element the handler was added to.
256+ // Since we're adding the handler to all elements our content wants
257+ // to listen for events on, this ensures that we always get the right
258+ // values for `a` and `b`.
259+ const type = event.type;
260+ const a = this["dodrio-a-" + type];
261+ const b = this["dodrio-b-" + type];
262+ trampoline(event, a, b);
263+ }
279264 }
280265}
281266window.ChangeList = ChangeList;