0

Consider the following examples:

const buttons = [...document.querySelectorAll("button")];

const fn = i => console.log(i);

window.addEventListener("click", ({target}) => {
  const i = buttons.indexOf(target);
  
  i && fn(i)
});

buttons.forEach((button, i) => button.onclick = () => fn(i))
<button>button</button>
<button>button</button>
<button>button</button>
<button>button</button>
<button>button</button>
<button>button</button>

In the first example, we apply just one listener, but it will get called even though the click is not on a button.

In the second example, we apply multiple listeners, but the function will get called only if the click happened on a button.

But both of them are using the same function fn.

Those are very basic examples, but ideal to explain my question. If we can have unknown number of buttons, does it make any difference in performance than setting up just one listener to the window?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Gergő Horváth
  • 3,195
  • 4
  • 28
  • 64
  • I really don't understand why your first example is just an event listener on the window, but... you're asking if there's a performance difference between event delegation and attaching to an HTMLElement directly? – zfrisch Aug 01 '19 at 16:17
  • If your window contains buttons only - there would be no difference. – PM 77-1 Aug 01 '19 at 16:18
  • Note that your example fails to take advantage of one of event delegation's benefits; you don't need to know ahead of time if the element exists. You perform the checking in the event handler. For instance, you could do `if (target.tagName === 'button') { /* do something for any button */ } if (target.id === 'mySpecialElement') { /* do something for #mySpecialElement, which might not have existed when I attached the event handler */ }`... – Heretic Monkey Aug 01 '19 at 16:23

3 Answers3

2

In theory having one listener on document would be less performance intensive than putting one on each button only if you have event propagation in the buttons. The way events work in the DOM is when you click on an element it emits an event which can be listened for. This element will then tell the parent element that it was clicked on and the parent element will emit the same event, propagating upwards to the window.

All of that said, the performance difference isn't close to large enough to justify worrying about it unless you have that many buttons. Having individual listeners on the buttons just makes it easier to understand from a code perspective. Having individual listeners is the better practice unless the page dynamically adds buttons.

EDIT: Event Propagation can be stopped using event.stopPropagation()

KolCrooks
  • 514
  • 1
  • 5
  • 12
  • Good answer. In normal coding practice worrying about this really is a micro-optimization of the highest order. – zfrisch Aug 01 '19 at 16:23
  • 1
    _"Having individual listeners on the buttons just makes it easier to understand from a code perspective"_ I don't agree with this. it's only true if you have static content. As soon as you start manipulating the DOM and adding elements then it's clearer to keep the functionality that reacts to them separate from the template for the elements, IMO. – Adam Aug 01 '19 at 16:30
  • @Adam you are right about that. I made an edit to correct it. – KolCrooks Aug 01 '19 at 16:39
1

It's much better in terms of performance to set up a listener on the parent. But as already mentioned, this is only something to worry about if you are handling a lot of events (e.g. turning a large array of items into a list of DOM elements with click handlers).

const fn = i => console.log(i);
const container = document.getElementById('container');
const children = container.children;
const childrenArray = Array.from(children);

container.addEventListener('click', event => {
  const {target} = event;
  if(target !== container){
    fn(childrenArray.indexOf(target));
  }  
})
<div id="container">
  <button>button</button>
  <button>button</button>
  <button>button</button>
  <button>button</button>
  <button>button</button>
  <button>button</button>
</div>
GenericUser
  • 3,003
  • 1
  • 11
  • 17
1

If you have other Javascript that manipulates the DOM and, for example, adds new buttons then the single listener on the window will still fire for the new buttons. Whereas the per-element listener will only apply to buttons that were in the DOM at the point you added the listeners. So, you then have to know in the code which manipulates the page what listeners should exist and add them to new elements. The single listener is a far better solution from this point of view, in my opinion.

Adam
  • 6,539
  • 3
  • 39
  • 65