Skip to content

Instantly share code, notes, and snippets.

@JeremyRH
Last active April 2, 2024 18:43
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JeremyRH/d2ceaf6b66b8ecca214b7e242d158d5a to your computer and use it in GitHub Desktop.
Save JeremyRH/d2ceaf6b66b8ecca214b7e242d158d5a to your computer and use it in GitHub Desktop.
Does event delegation actually improve performance?

Does event delegation actually improve performance?

Event delegation works by attaching a single event listener to a parent element to catch events bubbling up from the children. Many people believe this is more performant than attaching event listeners to each child. I am not convinced this is always true.

Let's start with a common example of event delegation. Here we have a list of elements:

<ul id="item-list">
  <li data-cost="12">Item 1</li>
  <li data-cost="18">Item 2</li>
  <li data-cost="6">Item 3</li>
  ...
</ul>

We want to log the item cost when the user clicks an item. In order to do that, we loop through every item and add a unique event listener that reads the data-cost attribute:

for (const li of document.getElementById('item-list').children) {
  li.addEventListener('click', () => console.log(li.dataset.cost));
}

However, there is an issue with this code. Looping over an unknown amount of items can add additional processing time before the page is interactive and adding an event listener to every li will increase memory usage unnecessarily. To avoid that, we can add a single event listener to the ul that references the li using event.target:

document.getElementById('item-list').addEventListener('click', event => {
  console.log(event.target.dataset.cost);
});

That works, but what if our li elements become a little more complicated?

<li data-cost="12">
  <div>Item 1</div>
  <div class="description">Item one's description</div>
</li>

event.target might reference one of the li element's children depending on where the user clicks. This can be handled by searching for the closest parent that matches the li or checking the event's path. Downside is, this requires the event listener to account for the structure of your HTML.

Let's go back to the original solution and understand why attaching event listeners to every element is not performant. There are three parts to this theory:

  • Looping over a large set of elements adds additional time before the page is interactive.
  • Adding an event listener for every element slows down the page due to some overhead cost of having event listeners attached.
  • Memory usage is unnecessarily increased because a new function is created for every event listener.

The first argument is valid but only with extremely large sets of elememnts. A low-end smartphone user wont notice the difference between 10 and 1000 elements being looped over. There may be issues once you reach tens of thousands of elements, but the cost of sending that HTML and the browser rendering it will far outweigh the 100ms+ cost of attaching event listeners.

The second argument might have been true at one time, but modern browsers do not suffer from having many event listeners attached. The performance overhead for having an event listener attached is only realized when the specific event is dispatched, not when idle or other events are dispatched.

This third argument is valid, but simply reusing the same function for every element solves the issue:

const listener = event => console.log(event.currentTarget.dataset.cost);
for (const li of document.getElementById('item-list').children) {
  li.addEventListener('click', listener);
}

Event delegation does have an advantage by handling elements added dynamically during the lifetime of a page but you should consider attaching event listeners when the element is created. This reduces implicit coupling between elements (ul and li in the example).

Another bonus is an improvement when debugging. Some browser debugging tools allow you to see the event listeners attached from the elements panel. Having the event listener directly attached to the element can be a time saver.

So, does event delegation actually improve performance?

Yes, by not looping over an undefined number of elements, the page's time to interactivity is reduced. It could save you one millisecond or a thousand. Do some testing to see if it's really worth it.

@oelna
Copy link

oelna commented Oct 28, 2022

Thank you for writing this up

@Sor3nt
Copy link

Sor3nt commented Jan 4, 2023

thank you!

@librz
Copy link

librz commented Oct 30, 2023

Memory usage is unnecessarily increased because a new function is created for every event listener.

This is true if the event listener function is inlined. When using a pre-setup, common function as event handler, memory usage is idential to that of event delegation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment