Is there a more efficient solution here?
Yes, either of your first two solutions. :-) Details:
At first I thought of using a key-value pair object, but DOM elements cannot be substituted as object keys.
That's true, but they can be Map
object keys. They can even be WeakMap
object keys if you need to prevent the Map
from keeping the element in memory.
You do need to target environments that have Map
and WeakMap
(all current major ones do), although there are some really powerful polyfills out there doing cool things to emulate Map
behavior in a clever, well-performing way.
Example:
// See https://stackoverflow.com/questions/46929157/foreach-on-queryselectorall-not-working-in-recent-microsoft-browsers/46929259#46929259
if (typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
NodeList.prototype.forEach = Array.prototype.forEach;
}
const map = new WeakMap();
document.querySelectorAll("textarea").forEach((element, index) => {
++index;
map.set(element, {name: "TextArea #" + index});
});
document.addEventListener("click", event => {
const entry = map.get(event.target);
if (entry) {
console.log("The name for the textarea you clicked is " + entry.name);
}
});
<div>Click in each of these text areas:</div>
<textarea>one</textarea>
<textarea>two</textarea>
I could instead try to generate a unique CSS selector for each DOM element and then use that selector as the object key, but this approach seems very expensive.
It isn't if you use id
for that: Give yourself a unique prefix and then following it with an ever-increasing number. (And if you need to go from the ID to the element, getElementById
is blindingly fast.)
Example:
// See https://stackoverflow.com/questions/46929157/foreach-on-queryselectorall-not-working-in-recent-microsoft-browsers/46929259#46929259
if (typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
NodeList.prototype.forEach = Array.prototype.forEach;
}
let nextIdNum = 1;
const pseudoMap = Object.create(null);
document.querySelectorAll("textarea").forEach((element, index) => {
if (!element.id) {
element.id = "__x_" + nextIdNum++;
}
++index;
pseudoMap[element.id] = {name: "TextArea #" + index};
});
document.addEventListener("click", event => {
const entry = pseudoMap[event.target.id];
if (entry) {
console.log("The name for the textarea you clicked is " + entry.name);
}
});
<div>Click in each of these text areas:</div>
<textarea>one</textarea>
<textarea>two</textarea>
If you use id
for other purposes, it can be a data-*
attribute instead. (And if you need to go from the data-*
attribute value to the element, querySelector
on a data-*
attribute isn't all that expensive.)
Example:
// See https://stackoverflow.com/questions/46929157/foreach-on-queryselectorall-not-working-in-recent-microsoft-browsers/46929259#46929259
if (typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
NodeList.prototype.forEach = Array.prototype.forEach;
}
let nextIdNum = 1;
const pseudoMap = Object.create(null);
document.querySelectorAll("textarea").forEach((element, index) => {
const id = "__x_" + nextIdNum++;
element.setAttribute("data-id", id);
pseudoMap[id] = {name: "TextArea #" + index};
});
document.addEventListener("click", event => {
const entry = pseudoMap[event.target.getAttribute("data-id")];
if (entry) {
console.log("The name for the textarea you clicked is " + entry.name);
}
});
<div>Click in each of these text areas:</div>
<textarea>one</textarea>
<textarea>two</textarea>