Event Bubbling & Event Delegation in JavaScript
Yesterday, I learned how JavaScript uses events to respond to user interactions.
Today, I went one level deeper and explored how events actually travel through the DOM.
I learned about Event Bubbling, Event Capturing, stopPropagation(), and Event Delegation.
These concepts become extremely useful when working with multiple elements and dynamic content.
Let's dive in.
π What is Event Bubbling?
When an event occurs on an element, it doesn't necessarily stay there.
The event can travel upward through its parent elements.
This process is called Event Bubbling.
For example:
<div id="parent">
<button id="child">Click Me</button>
</div>
If we click the button, the event can travel:
Button
β
Parent
β
Body
β
HTML
β
Document
This upward movement is event bubbling.
π Understanding Event Bubbling
Consider:
const parent = document.querySelector("#parent");
const child = document.querySelector("#child");
parent.addEventListener("click", function () {
console.log("Parent clicked");
});
child.addEventListener("click", function () {
console.log("Button clicked");
});
When the button is clicked, the output will be:
Button clicked
Parent clicked
The event starts at the button and then bubbles up to its parent.
π stopPropagation()
Sometimes we don't want an event to continue bubbling.
We can stop it using:
child.addEventListener("click", function (event) {
event.stopPropagation();
console.log("Button clicked");
});
Now the event doesn't continue to the parent.
This can be useful when nested elements have different event behavior.
β¬οΈ Event Capturing
Event capturing is the opposite direction of event bubbling.
Instead of moving from the target upward, the event travels from the document down toward the target.
The general flow is:
Document
β
HTML
β
Body
β
Parent
β
Button
By default, event listeners use the bubbling phase.
Capturing Phase
We can enable capturing by passing true as the third argument.
parent.addEventListener("click", function () {
console.log("Parent");
}, true);
Now the parent listener runs during the capturing phase.
π― Event Target vs Current Target
These two properties are important.
event.target
The element that actually triggered the event.
event.currentTarget
The element whose event listener is currently executing.
Example:
parent.addEventListener("click", function (event) {
console.log(event.target);
console.log(event.currentTarget);
});
If the button is clicked:
event.targetβ buttonevent.currentTargetβ parent
Understanding this difference becomes very useful with event delegation.
π What is Event Delegation?
Event Delegation is a technique where we attach one event listener to a parent instead of adding separate listeners to every child.
For example, instead of:
button1.addEventListener("click", handler);
button2.addEventListener("click", handler);
button3.addEventListener("click", handler);
We can attach one listener to their parent.
Event Delegation Example
HTML:
<ul id="list">
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
JavaScript:
const list = document.querySelector("#list");
list.addEventListener("click", function (event) {
if (event.target.tagName === "LI") {
console.log(event.target.textContent);
}
});
Now one event listener handles all the list items.
Why is Event Delegation Useful?
Imagine a shopping cart with 100 products.
Adding a separate event listener to every button isn't always the best approach.
Instead, we can attach one listener to the parent container and determine which child was clicked.
This can make event handling more efficient and easier to manage.
Dynamic Elements
Event delegation becomes especially useful when elements are created dynamically.
For example:
const list = document.querySelector("#list");
const item = document.createElement("li");
item.textContent = "JavaScript";
list.appendChild(item);
Because the listener is attached to the parent, it can handle events from dynamically added children too.
matches() Method
We can use matches() to check whether an element matches a CSS selector.
list.addEventListener("click", function (event) {
if (event.target.matches("li")) {
console.log(event.target.textContent);
}
});
This makes event delegation cleaner and more flexible.
Common Event Handling Concepts
| Concept | Meaning |
|---|---|
| Event Bubbling | Event travels from child to parent |
| Event Capturing | Event travels from parent to child |
stopPropagation() |
Stops event propagation |
event.target |
Element that triggered the event |
event.currentTarget |
Element handling the event |
| Event Delegation | Parent handles events for its children |
Best Practices
β Use event delegation when many similar elements need the same event handling.
β Understand event.target and event.currentTarget.
β Use stopPropagation() only when necessary.
β Avoid unnecessarily attaching hundreds of individual listeners.
β Keep event-handling logic clear and focused.
My Biggest Takeaway
Today, I learned that JavaScript events are more than simply clicking a button.
Events travel through the DOM, and understanding that process gives me much more control over how my applications respond to user interactions.
The concept that stood out the most was Event Delegation.
Instead of attaching listeners to every individual element, I can use a parent element to handle events from its children.
This is a powerful technique that I'll definitely use when building larger and more dynamic applications.
