Do Javascript engines do this kind of optimisation?

NB I’ve done a bit of reading about JS engine optimisation, but most of it is too technical for me to understand enough to apply to this question. I’m also aware that not all engines are the same. I’d be interested in particular in handling by V8 and Rhino, I suppose.

If I create a table, and then rows, and then cells… And then I want to put identical key event listeners on all those cells.

Not only does the creation of these listeners for each cell take a certain amount of time, which could be significant with a biggish table, but in addition I’m supposing that each listener function is stored on its own, even though every listener function is actually identical.

The other key event listener approach which I can use is to put a key event listener on the TABLE, and to work out during the run, on each keydown event, which cell fired this event. I can do this by going

let elementOfInterest = document.activeElement;

“Get the currently focused element in the document” from here.

From my experiments, if you type inside a table cell, this TD does indeed have the focus and is indeed returned by the above call.

This way, I only have to create one listener, which will I assume be quicker and take less memory. The only (very) slight downside is that time then has to be spent getting this “active element” by means of the above call. And, just possibly, the risk that something will grab focus in an unexpected way – obviously if you want to listen to changes of text in a cell, the least error-prone technique must be to use a listener attached to that cell.

But I’m just wondering: maybe Javascript is cleverer than this: maybe if you create 100 separate cell listeners something somewhere identifies them as “all the same” and just makes one function in memory. This is the kind of optimisation you might typically expect from a Java compiler, for example.

Does any such optimisation ever occur? How clever is Javascript with a case like this? Or is it just “script and that’s it”: what you see is what you get?

Answer

The semantics of the language itself don’t allow for two function expressions to be “merged” into one even if they were functionally equivalent:

> a = function(){return 'foo'};
ƒ (){return 'foo'}
> b = function(){return 'foo'};
ƒ (){return 'foo'}
> a === b
false

In addition, things get extra hairy when you start considering the closure of the function (e.g. the outer names it uses).

So no, that doesn’t happen out of the box.

However, for your use case, there are two optimizations:

  • As you’ve found out, you can employ event bubbling and add the event listener on an ancestor element and use event.target (preferably instead of document.activeElement) to figure out where it was originally targeted (and event.currentTarget would be the node the handler is on)
  • If you can’t use a common ancestor (tip: you almost always can; document is a valid target), you could define the function once (assuming it doesn’t need to close over any dynamically changing variables) and again use event.target, e.g. event.target.dataset to figure out the data you’re handling.

Below, a snippet demonstrating the two.

function createButton(parent, datum) {
  const btn = document.createElement("button");
  btn.dataset.datum = datum;
  btn.innerHTML = datum;
  parent.appendChild(btn);
  return btn;
}

function eventHandler(event) {
  if(event.target.tagName !== "BUTTON") return;
  const msg = `real target: ${event.target} (datum="${event.target.dataset.datum}")ncurrent target: ${event.currentTarget}`;
  alert(msg);
}

const p2 = document.getElementById("parent2");

// bubbling listener
const p1 = document.getElementById("parent1");
p1.addEventListener("click", eventHandler, false);
for(var i = 0; i < 10; i++) {
  createButton(p1, "p1-" + i);
}


// same function on multiple elements
for(var i = 0; i < 10; i++) {
  createButton(p2, "p2-" + i).addEventListener("click", eventHandler, false);
}
<div id="parent1"></div>
<div id="parent2"></div>