Course: JavaScript Intermediate
Lesson 10: DOM Events in Depth
Level
Intermediate
Estimated Reading Time
10–15 minutes
Goal
By the end of this lesson, the learner will be able to:
- Attach and remove event listeners using
addEventListenerandremoveEventListener - Access key properties on the event object such as
target,type, andkey - Prevent default browser behavior and stop event propagation when needed
Frontend Development Context
Events are how users interact with a webpage. Every click, keypress, form submission, and scroll is an event. The addEventListener approach is the standard for handling events in modern JavaScript — it is more flexible than inline HTML event attributes and allows multiple listeners on the same element.
Going deeper into events means understanding the event object that gets passed to your callback, knowing how to clean up listeners when they are no longer needed, and understanding propagation — the process by which events bubble up through the DOM tree. These skills are required for building forms, modals, keyboard shortcuts, and accessible interactive components.
Explanation
addEventListener(type, callback) attaches a function to an element that runs whenever the specified event occurs. The first argument is the event type as a string ("click", "keydown", "submit", "input", etc.). The second argument is the callback function that receives the event object.
The event object contains information about what happened: event.type is the event name, event.target is the element that triggered the event, event.key is the key pressed (for keyboard events), and event.preventDefault() stops the browser's default behavior (such as form submission or following a link).
removeEventListener removes a listener from an element. For this to work, the callback passed to removeEventListener must be the same function reference that was passed to addEventListener. Anonymous functions cannot be removed because there is no reference to them.
Events bubble by default — when a click happens on a child element, the click event travels up through each ancestor element. You can stop this with event.stopPropagation(). The third parameter of addEventListener can be { once: true } to automatically remove the listener after it fires once, or { capture: true } to listen during the capture phase instead of the bubble phase.
Code Example
This example adds click and keyboard listeners, reads the event object, and demonstrates removing a listener.
const button = document.querySelector("#submitBtn");
const input = document.querySelector("#searchInput");
// Named function so we can remove it later
function handleClick(event) {
console.log("Event type:", event.type); // "click"
console.log("Clicked element:", event.target); // the button element
event.target.textContent = "Clicked!";
}
button.addEventListener("click", handleClick);
// Keyboard event — reading event.key
input.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
console.log("Search for:", input.value);
}
if (event.key === "Escape") {
input.value = "";
input.blur();
}
});
// Remove the listener after 5 seconds
setTimeout(() => {
button.removeEventListener("click", handleClick);
console.log("Click listener removed");
}, 5000);
// Once option — fires only one time
button.addEventListener("mouseover", () => {
button.style.background = "lightblue";
}, { once: true });
Code Explanation
handleClick is a named function stored in a variable so it can be passed to both addEventListener and later removeEventListener. If it were written as an anonymous function inline, removeEventListener would not work because there would be no reference to match.
The keydown listener reads event.key to determine which key was pressed. "Enter" triggers a search action, and "Escape" clears the input and removes focus. The key property gives you the human-readable key name, not a numeric code.
removeEventListener inside setTimeout removes the click handler after 5 seconds. This simulates a real-world pattern where you clean up event listeners to prevent memory leaks — for example, when a modal closes or a component unmounts.
The { once: true } option on the mouseover listener means the handler runs once and is automatically removed. This avoids the need to manually call removeEventListener when you only want a one-time effect.
Pattern Highlights
✅ Positive Pattern: Use named functions when you need to remove an event listener later. Store the function in a variable so you have a reference to pass to
removeEventListener.
⚠️ Neutral Note: Event bubbling allows a parent element to receive events from its children. This is usually the behavior you want, but call
event.stopPropagation()when you need to prevent a child event from triggering a parent handler unintentionally.
❌ Negative Pattern: Do not add event listeners inside a loop without a cleanup strategy. Adding listeners to many elements without removing them when they are no longer needed can cause memory leaks in long-running applications.
Common Mistakes
Mistake 1: Trying to remove an anonymous event listener
Using an anonymous function with removeEventListener — it never works.
button.addEventListener("click", () => console.log("clicked"));
button.removeEventListener("click", () => console.log("clicked")); // does nothing
Each () => creates a new function object. The one passed to removeEventListener is a different reference from the one attached. Use a named function stored in a variable instead.
Mistake 2: Forgetting event.preventDefault() on form submission
Allowing the browser to reload the page when a form is submitted.
form.addEventListener("submit", function (event) {
// forgot event.preventDefault()
validateForm(); // runs, but page reloads immediately and discards the result
});
Without event.preventDefault(), the browser performs its default form submission (a page reload or navigation). Call it at the top of the submit handler to keep the app in control.
Mistake 3: Accessing event.target properties without checking the element type
Assuming event.target is always the element you attached the listener to.
list.addEventListener("click", function (event) {
console.log(event.target.dataset.id); // may be undefined if a child element was clicked
});
When a list item contains child elements (an icon, a span), clicking the child sets event.target to the child, not the list item. Check event.target.closest("li") or use event.currentTarget to reference the element the listener is attached to.
Quick Recap
addEventListener(type, callback)attaches an event handler; the callback receives an event object with details about what happened- Use named functions when you need to remove a listener with
removeEventListener event.targetis the element that triggered the event;event.preventDefault()stops default browser behavior;event.stopPropagation()stops bubbling- The
{ once: true }option automatically removes the listener after it fires once