What Is Dom Event Delegation

Why should I use event delegation instead of queryselectorAll

A few benefits:

  • If you have 1000 buttons, it's better to add one click handler on an ancestor element that listens for a click on the descendant buttons, than going through all 1000 buttons and attaching a handler each. The iteration through 1000 elements alone is a performance issue, worse if more. Not to mention the memory usage involved for 1000 buttons to reference the handler.

  • You can listen for events on an element that may not exist yet or doesn't exist yet. You can attach a click handler ahead of time for a descendant button that may not be there. This is commonly used when the content being listened is loaded via AJAX.

  • You can persist handlers across DOM changes. Usually used with dynamic content. For instance, you can a click handler for a todo list item's delete button once. Every time you attach/reattach/detach a todo list item, you don't have to clean up its handlers. You can leave the delegated one alone.

Delegation, is just that, to delegate, to entrust to another. In this case, it's handing the responsibility of holding the event handler from an element to its ancestor. And for this to work, it has to use bubbling.

And bubbling is just half the story. The opposite of bubbling is capturing.

DOM event delegation or not, which is best resourcewise?

The principal performance advantages of event delegation in the case you describe are

  1. You only have to hook up a handler once, rather than looping through all the rows to do it. But if you have a table so big that hooking up the handlers to the buttons is a slow operation, you're likely to have other performance issues with that table.

  2. A minor memory savings (the event registrations).

There isn't a performance disadvantage to worry about if it's a click handler. (That might not be true of, say, a mousemove handler.)

There are code complication and maintenance issues to consider (delegated handler is slightly more complicated to write; hooking up the handler to each button is easier to get wrong if buttons are added dynamically at some point), but you've said you're principally concerned about performance.

In the (1) delegate case, doesn't the browser have to somewhat add listeners to every child element to know it has been clicked ?

No, just the one event listener. What happens is that clicks bubble. Here's a diagram from the old DOM3 events spec (I can't find this diagram or an equivalent in the now-canonical WHAT-WG DOM spec, which is unfortunate):

Sample Image

The handler is only attached to the one element. It's found when the click bubbles (propagates) to that element.

  1. Use a single delegate click handler on the <table> element ? This would be triggered by any click on any other cell or sub-element and basically triggered many more times for just nothing.

That's true, but it's harmless. The amount of work required is virtually nil.


  1. Use an event handler on every <button> on every row ? This would add many handlers but each would run only once when needed.

You don't need many handlers, you can reuse the same function for multiple buttons. It's just multiple registrations of handlers:

function buttonHandler(event) {
// ...
}
// ...
for (const btn of document.querySelectorAll(".selector-for-button")) {
btn.addEventListener("click", buttonHandler); // Adds a registration, not
// an additional function
}

That doesn't create a new function for each button, it reuses the same function for all of the buttons. During the call, event.currentTarget will refer to the specific button that was clicked (the one we hooked up the handler to). (If the button has child elements, event.target may refer to one of those children, but event.currentTarget will refer to the button.)

So in any given situation, you have to weigh those various considerations to decide what you want to do.

Class-based DOM event delegation

Although considered bad practice (because you should generally delegate to the nearest static ancestor), you can delegate to the body element as follows:

$('body').on('click', '.my-class', function() {
alert('clicked');
});

This will of course delegate click events on any elements with a class of my-class that are in the body of your document.

Vanilla JavaScript event delegation when dealing with Web Components

If the shadow DOM mode is open, it is possible to get the inner element clicked with the help of the Event.composedPath() method, which will return the array of the nodes crossed (innest node first).

document.addEventListener('click', oEvent => {
result.innerText = oEvent.composedPath()[0].tagName;
}, true);

This method replaces the old Event.path property.

customElements.define('custom-el', class extends HTMLElement {  constructor() {    super();    this._shadowRoot = this.attachShadow({ mode: 'open' });    const oInnerDiv = document.createElement('div');    oInnerDiv.classList.add('inner');    oInnerDiv.style.border = '2px solid blue';    oInnerDiv.style.padding = '1rem';  this._shadowRoot.appendChild(oInnerDiv);        }});
document.addEventListener('click', oEvent => { result.innerText = oEvent.composedPath()[0].tagName;});
html { box-sizing: border-box;}
*,*::before,*::after { box-sizing: inherit;}
body { margin: 0; padding: 0;}
main,div,custom-el { display: inline-block; border: 2px solid black; padding: 1rem;}
<main>    <custom-el></custom-el></main>  <p id="result"></p>

Event delegation vs direct binding when adding complex elements to a page

You will create less CPU overhead in binding the events using $(<root-element>).on(<event>, <selector>) since you will be binding to a single "root" element instead of potentially many more single descendant elements (each bind takes time...).

That being said, you will incur more CPU overhead when the actual events occur as they have to bubble up the DOM to the "root" element.

Short-story: delegate saves CPU when binding event handlers; bind saves CPU when events trigger (e.g. a user clicks something).

So it's up to you to decide what point is more important for performance. Do you have available CPU when you add the new elements? If so then binding directly to the new elements would be the best for overall performance however if adding the elements is a CPU intensive operation you will probably want to delegate the event binding and let the event triggering create some extra CPU overhead from all the bubbling.

Note that:

$(<root-element>).on(<event>, <selector>, <event-handler>)

is the same as:

$(<root-element>).delegate(<selector>, <event>, <event-handler>)

and that:

$(<selector>).on(<event>, <event-handler>)

is the same as:

$(<selector>).bind(<event>, <event-handler>)

.on() is new in jQuery 1.7 and if you are using 1.7+ then .delegate(<selector>, <event>, <event-handler>) is just a short-cut for .on(<event>, <selector>, <event-handler>).

UPDATE

Here is a performance test showing that it is faster to delegate event binding than to bind to each element individually: http://jsperf.com/bind-vs-click/29. Sadly this performance test has been removed.

UPDATE

Here is a performance test showing that event triggering is faster when you bind directly to elements rather than delegate the binding: http://jsperf.com/jquery-delegate-vs-bind-triggering (Note that this isn't a perfect performance test since the binding methods are included in the test, but since delegate runs faster on binding it just means that bind is even faster relatively when talking about triggering)

Why does Event Delegation save memory?

Somewhere in the browser's memory, there's a data structure that contains the list of event listeners for each element. If you call addEventListener() separately for 100 different elements, it will create 100 entries in this table.

But if you use event delegation, you only have to call addEventListener() once, so there's only 1 entry in the table.

You can actually see a representation of this data by opening the Event Listeners tab in the Elements panel of Developer Tools.

However, the amount of memory you save is probably not very significant. Each listener is probably just a couple of pointers, one to something representing the event type (click, change, etc.) and another to the callback function. If all 100 event listeners call the same function, there's just one function object and 100 pointers to it. If the function is a closure, there will also be an environment object containing the variables it closes over, which will add a little more memory use, but not very much.

On the other hand, when you use delegation, the callback function needs to do extra work to determine if the event target is an appropriate nested element. This makes it a little slower. It will also be called if the event is triggered on an element that's in the container element but not one of the elements you're delegated to (and will run repeatedly as the event bubbles out), so the function is run more often. If memory were really at a premium, this would be a classic time/space tradeoff.

In practice, delegation isn't used to save memory, it's used to simplify the design. It's most often used when you're adding elements to the DOM dynamically, or changing attributes (e.g. class names) that the event binding depends on. Delegation allows you to define the event listener once, rather than having to add or remove it from elements as they're added or modified.



Related Topics



Leave a reply



Submit