Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for where to add event listeners

On my page, the user clicks on an element in order to edit it. To facilitate this, I assign the class editable to all such elements.

How should I listen for clicks on all these elements? Currently, I'm doing this:

document.body.addEventListener("click", (event) => {
  if (event.target.classList.contains("editable")) {
    // do stuff
  }
});

The alternative would be to set a listener on every element, like this:

const editables = document.getElementsByClassName("editable");
for (const editable of editables) {
    editable.addEventListener("click", editElement);
}

It seems to me that the first way must be better for performance, since it's only one element being listened on, but is it possible to degrade performance by attaching all such events to the body element? Are there any other considerations (e.g. browser implementations of event handling) that I'm neglecting which would suggest doing it the second way?

like image 767
Chris Middleton Avatar asked Sep 29 '14 16:09

Chris Middleton


1 Answers

Short answer: definitely do it the first way. Event delegation is way more performant, but requires extra conditionals in your code, so it's basically a complexity versus performance tradeoff.

Longer Answer: For a small number of elements, adding individual event handlers works fine. However, as you add more and more event handlers, the browser's performance begins to degrade. The reason is that listening for events is memory intensive.

However, in the DOM, events "bubble up" from the most specific target to the most general triggering any event handlers along the way. Here's an example:

<html>
    <body>
        <div>
            <a>
                <img>
            </a>
         </div>
    </body>
</html>

If you clicked on the <img> tag, that click event would fire any event handlers in this order:

  1. img
  2. a
  3. div
  4. body
  5. html
  6. document object

Event delegation is the technique of listening to a parent (say <div>) for a bunch of event handlers instead of the specific element you care about (say <img>). The event object will have a target property which points to the specific dom element from which the event originated (in this case <img>).

Your code for event delegation might look something like this:

$(document).ready(function(){
    $('<div>').on('click', function(e) {
        // check if e.target is an img tag
        // do whatever in response to the image being clicked
    });
});

For more information checkout Dave Walsh's blog post on Event Delegation or duckduckgo "event delegation".

NOTE ON CODE SAMPLE IN OP: In the first example, target.hasClass('editable') means that the specific thing clicked on must have the class editable for the if block to execute. As one of the commenters pointed out, that's probably not what you want. You might want to try something along these lines instead:

$(document).on('click', function(e) {
   if ($(e.target).parents(".editable").length) {
       // Do whatever
   }
});

Let's break that down a bit:

  • $(e.target) - anything that on the page that was clicked converted to jQuery
  • .parents(".editable") - find all the ancestors of the element clicked, then filter to only include ones with the class "editable"
  • .length - this should be an integer. If 0, this means there are no parents with "editable" class
like image 178
Rustavore Avatar answered Sep 23 '22 13:09

Rustavore