Event Delegation
Attaching one listener to a stable ancestor, using event bubbling to handle events from any current or future descendant.
ancestor.addEventListener(type, (e) => { const match = e.target.closest(selector); })
Purpose
Event delegation attaches a single listener to a stable ancestor element instead of one listener per descendant, relying on bubbling (recall addEventListener and the Event Object) to handle events from any current or future descendant with no extra wiring.
Example
const list = document.querySelector('#task-list');
list.addEventListener('click', (event) => {
const button = event.target.closest('button[data-action]');
if (!button) return; // click didn't originate on/inside a relevant button
const action = button.dataset.action;
// handle action…
});
Behavior notes
event.targetis the exact element the event originated on, which may be a descendant of the element actually of interest (an icon inside a button, for instance) —.closest(selector)walks up fromevent.targetto find the nearest matching ancestor, or the element itself.- A guard clause (
if (!button) return;) is essential — the delegated listener fires for every click inside the ancestor, including ones that don’t match anything relevant. - Elements added to the ancestor after the listener was attached are handled automatically, since the listener isn’t tied to any specific descendant.
Common mistakes
- Forgetting the guard clause, and running action logic for clicks that didn’t actually originate on a relevant element.
- Delegating to
documentwhen a much closer, stable ancestor would work — unnecessarily wide delegation makes the guard-clause matching logic do more work than needed.
Runtime notes
Built on core DOM event bubbling — browser-only.