Home > Mobile >  Is using a variable in an onclick worse than using e.target?
Is using a variable in an onclick worse than using e.target?

Time:06-30

Wondering if there is a performance/memory area I could improve when setting onclick listeners to elements.

Example:

let btn = document.querySelector('.example')
btn.addEventListener('click', (e) => {
    btn.classList.add('active');
    btn.onmouseleave = () => {
        console.warn('leave')
    }
})

compared to:

let btn = document.querySelector('.example')
btn.addEventListener('click', (e) => {
    e.target.classList.add('active');
    e.target.onmouseleave = () => {
        console.warn('leave')
    }
})

I would think that using btn would take a lot more work as the computer has to save the variable and remember it, whereas e.target uses the event temporarily. Which one is best for the least amount of memory consumption?

CodePudding user response:

TL;TiM: Event delegation* is better.

Event target vs currentTarget

Event.target might not be your btn Button.
Say the click casually landed on an icon inside of your button <button type="button">Contact <i ></i></button>, the Event.target would be — your icon!
To get your button Element (on which the handler is attached) use Event.currentTarget.

The rule of thumbs is really simple:

  • Always use Event.currentTarget (even if suggested differently, unless you really, really know what you're doing)
  • Use Event.target when in need to do stuff like: getting a closest ancestor (or self) from where a click landed. I.e: if (!evt.target.closest("#popup")) // Click landed outside of #popup. Close the popup - or when using Event delegation (more on that later).

Variable vs. Argument performance

There's not much difference in performance in using a variable holding a single Element vs. the Event argument, other than perhaps memory and engine implementation.
If btn is a single Element stored in a variable, it already is the currentTarget: btn === e.currentTarget // always true therefore it's eventually up to your use case, and that's if you need to reuse the btn variable in other parts of the app, which would reduce queries to the DOM.
If instead btns is a huge NodeList, such as the one given back by const btns = ancestorElement.querySelectorAll("selector"), memory could be a factor.

on* event handlers are arguably bad

onmouseleave and other on* handlers should not be used - unless you're creating (in a really small and readable script) brand new Elements from in-memory, or you intentionally want to override a previously assigned on* handler listener of the same eventName.

Use EventTarget.addEventListener() instead with the third argument options {once: true} (for if an event handler is needed only once).

Third, improved version:

const addActive = (evt) => {
  const elBtn = evt.currentTarget;
  elBtn.classList.add("active");
  elBtn.addEventListener("pointerleave", () => {
    console.log("leave");
  }, {once: true});
};

// Since you're using classes instead of ID,  
// make sure to refer to All of your elements by class,
// and assign an event listener to all of them:

const elsBtns = document.querySelectorAll('.example');

elsBtns.forEach((elBtn) => {
  elBtn.addEventListener("click", addActive);
});

or if the "mouseleave" or "pointerleave" is always needed:

const handleClick = (evt) => {
  const elBtn = evt.currentTarget;
  elBtn.classList.add("active");
};

const handleLeave = () => {
  console.log("leave");
};

const elsBtns = document.querySelectorAll('.example');

elsBtns.forEach((elBtn) => {
  elBtn.addEventListener("click", handleClick);
  elBtn.addEventListener("mouseleave", handleLeave);
});

Event delegation

Say you have thousands of buttons. Storing them all in a variable might consume loads of memory (unless till the value is garbage collected). What better solution do we have?
Instead of a NodeList collection of N elements hanging in memory, and assigning to every single one a function handler for a specific Event: target a single common, static parent or ancestor:

document.querySelector("#commonParent").addEventListener("click", (evt) => {
  const elBtn = evt.target.closest(".example");
  if (elBtn) {
    // a .example ButtonElement was clicked.
    // Do something:  
    elBtn.classList.add("active");
  }
});

Event delegation is not only nice because it's memory friendly, it also works for current of future Elements added into the DOM.

Also that's the other most used case when using specifically Event.target - but as you can see it's again in combination with the Element.closest() Method.

  • Related