
Event Delegation and DOM Event Bubbling in JavaScript
Attaching individual click event listeners to hundreds of buttons or list items inside a loop consumes unnecessary memory—and worse, any new item appended to the DOM later via AJAX or client-side rendering won't have the listener attached.
Event Delegation solves both problems by taking advantage of DOM Event Bubbling: when a user clicks a child button, that click event bubbles up through its ancestors. By attaching a single listener to the parent container and inspecting event.target.closest(), you handle current and future child elements with one function.
Example
Without Event Delegation:
- 200 list items = 200 addEventListener calls
- Dynamically added 201st item = does not respond to clicks
With Event Delegation on <ul>:
- 200 list items = 1 addEventListener call on the parent <ul>
- Dynamically added 201st item = works automatically
Input : Dynamic <ul class="tag-filter-list"> containing <button data-tag="..."> items
Output : Single delegated click listener on the parent list.
Implementation
<ul id="tag-list" class="tag-list">
<li><button type="button" class="tag-btn" data-tag="html">HTML5</button></li>
<li><button type="button" class="tag-btn" data-tag="css">CSS3</button></li>
<li><button type="button" class="tag-btn" data-tag="javascript">JavaScript</button></li>
</ul>
var tagList = document.getElementById("tag-list");
if (tagList) {
tagList.addEventListener("click", function (event) {
// Find the closest matching button even if the user clicked a nested <span> or icon
var button = event.target.closest(".tag-btn");
// Ignore clicks that did not originate inside a .tag-btn inside our list
if (!button || !tagList.contains(button)) {
return;
}
var selectedTag = button.getAttribute("data-tag");
console.log("Filtering articles by tag:", selectedTag);
});
}
Main Point: Use event.target.closest(selector) instead of checking event.target.matches(selector) directly so clicks on nested icons or <span> elements inside a button still resolve to the button cleanly.
Credits
Photo by Florian Olivo