JavaScript Events Advanced
Introduction
In this chapter, we’ll go deeper into how JavaScript handles events behind the scenes, including:
- Event bubbling vs capturing
event.target
vsevent.currentTarget
- Event delegation
- Preventing event propagation
Understanding these concepts is essential for building scalable, dynamic UIs — especially in lists, tables, forms, and SPAs.
Event Bubbling (Default Behavior)
By default, events bubble up the DOM tree — starting from the target element and moving to its ancestors.
<div id="parent">
<button id="child">Click Me</button>
</div>
document.getElementById("child").addEventListener("click", () => {
console.log("Child clicked");
});
document.getElementById("parent").addEventListener("click", () => {
console.log("Parent clicked");
});
Output if you click the button:
Child clicked
Parent clicked
👉 This is because the event “bubbles” from child to parent. Know more about event bubbles.
Stopping Bubbling
Use event.stopPropagation() to prevent the event from bubbling up.
document.getElementById("child").addEventListener("click", function (event) {
event.stopPropagation();
console.log("Only child clicked");
});
Now, clicking the button won’t trigger the parent’s listener.
Event Capturing (Trickier)
Capturing is the opposite of bubbling: the event goes top → bottom in the DOM before hitting the target.
Enable capturing with the 3rd parameter of addEventListener():
element.addEventListener("click", handler, true);
document.getElementById("parent").addEventListener("click", () => {
console.log("Parent Capturing");
}, true);
document.getElementById("child").addEventListener("click", () => {
console.log("Child Bubble");
});
Capturing runs first (if enabled), then bubbling.
event.target vs event.currentTarget
event.target
: The actual element that triggered the eventevent.currentTarget
: The element the handler is attached to
document.getElementById("parent").addEventListener("click", function (e) {
console.log("Target:", e.target); // What was clicked
console.log("Current Target:", e.currentTarget); // #parent
});
Event Delegation (Very Useful)
Instead of adding event listeners to multiple child elements, add one listener to their parent and check which child triggered it using event.target
.
Great for dynamic content like to-do lists or comment sections.
Example: Click on dynamic list items
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
</ul>
document.getElementById("myList").addEventListener("click", function (e) {
if (e.target.tagName === "LI") {
alert("You clicked " + e.target.textContent);
}
});
This works even if new <li>
items are added later — no need to bind again!
once Option (Run a Handler Only Once)
button.addEventListener("click", function () {
alert("You’ll see me only once!");
}, { once: true });
Combining It All: A Practical Example
<ul id="tasks">
<li>Task 1 <button>Delete</button></li>
<li>Task 2 <button>Delete</button></li>
</ul>
document.getElementById("tasks").addEventListener("click", function (e) {
if (e.target.tagName === "BUTTON") {
e.target.parentElement.remove(); // Remove <li>
}
});
This is cleaner and more efficient than assigning listeners to every button individually.
🧪 Practice Exercise:
Task:
- Demonstrate event bubbling with nested elements.
- Prevent bubbling with
stopPropagation
. - Use event delegation on a list where clicking an item changes its color.
- Create a "run once" button using
{ once: true }
. - Log
target
andcurrentTarget
and observe the difference.