19. Event Handling
Removing a listener requires the same function reference you registered with. Anonymous functions can't be removed.
What you'll learn
- 1Add/remove handlers with `addEventListener`.
- 2Read `target`, `type`, and other fields off the event object.
- 3Tell `preventDefault` from `stopPropagation`.
- 4Use event delegation to cut down on handlers.
- 5Understand the capture and bubble phases.
Overview
Removing a listener requires the same function reference you registered with. Anonymous functions can't be removed.
Core Concepts
1. addEventListener
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);Removing a listener requires the same function reference you registered with. Anonymous functions can't be removed.
2. The event object
The callback receives an `Event` object.
- `event.target`: the element the event actually happened on
- `event.currentTarget`: the element the listener is attached to
- `event.type`: "click", "input", etc.
- `event.key`, `event.code`: keyboard events
3. preventDefault / stopPropagation
- `preventDefault()`: blocks the default action (form submit, link navigation, etc.).
- `stopPropagation()`: prevents the event from bubbling up to ancestors.
4. Event delegation
When you have many children, attach one handler to the parent instead of one per child.
list.addEventListener("click", (e) => {
const li = e.target.closest("li");
if (!li) return;
console.log("clicked item:", li.textContent);
});5. Capture and bubble
Events flow window β target β parents (capture β bubble). Listeners fire in the bubble phase by default. Use `addEventListener(type, fn, { capture: true })` to register on the capture phase.
Examples
| File | What it covers |
|---|---|
| index.html | Example HTML |
| 01_addeventlistener.js | Register and remove a listener |
| 02_event_object.js | Reading the event object |
| 03_prevent_default.js | Using preventDefault |
| 04_delegation.js | Event delegation |
src/01_addeventlistener.js
/**
* Use addEventListener / removeEventListener.
*/
const btn = document.querySelector("#btn");
function onClick() {
console.log("button clicked");
}
btn.addEventListener("click", onClick);
// A listener that runs only once
btn.addEventListener("click", () => console.log("just once!"), { once: true });
src/02_event_object.js
/**
* Read information from the event object.
*/
const input = document.querySelector("#text");
input.addEventListener("input", (event) => {
console.log("type:", event.type);
console.log("target value:", event.target.value);
});
src/03_prevent_default.js
/**
* Block link navigation with preventDefault.
*/
const link = document.querySelector("#link");
link.addEventListener("click", (event) => {
event.preventDefault();
console.log("blocked link navigation.");
});
src/04_delegation.js
/**
* Handle clicks for many children with a single parent listener (delegation).
*/
const menu = document.querySelector("#menu");
menu.addEventListener("click", (event) => {
const li = event.target.closest("li");
if (!li || !menu.contains(li)) return;
console.log("selected:", li.textContent);
});
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Event handling</title>
</head>
<body>
<button id="btn">Click</button>
<a id="link" href="https://example.com">Link</a>
<ul id="menu">
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
<input id="text" type="text" placeholder="type" />
<script src="01_addeventlistener.js"></script>
<script src="02_event_object.js"></script>
<script src="03_prevent_default.js"></script>
<script src="04_delegation.js"></script>
</body>
</html>
Common Mistakes
- Using `onclick = fn` and being stuck with only one handler.
- Registering an anonymous function, then trying to remove it.
- Confusing `event.target` with `event.currentTarget`.
- Forgetting `preventDefault` on form submit and getting a page reload.
- Adding child elements dynamically and not realizing you need event delegation.
FAQ
Q1. What's the `once` option?
`{ once: true }` runs the listener exactly once and auto-removes it.
Q2. Passive listeners?
Register with `{ passive: true }` to allow scroll optimizations; preventDefault inside is ignored.
Q3. Is stopping propagation always good?
It can break other handlers β use it only when truly necessary.
Practice
Write a delegated handler that prints the text of whichever item in a list was clicked.
homework/README.md
Build a `<ul>` with 5 items and use a single delegated handler to print the text of the clicked item.
See `answer/homework_01.html` and `answer/homework_01.js`.
homework/answer/homework_01.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Homework 19-1</title>
</head>
<body>
<ul id="list">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
<li>Delta</li>
<li>Epsilon</li>
</ul>
<script src="homework_01.js"></script>
</body>
</html>
homework/answer/homework_01.js
/**
* Handle list clicks via delegation.
*/
const list = document.querySelector("#list");
list.addEventListener("click", (event) => {
const li = event.target.closest("li");
if (!li || !list.contains(li)) return;
console.log("clicked:", li.textContent);
});
Next Lecture
[20_Forms_and_Inputs](../20_νΌκ³Ό_μ λ ₯/) β Working with forms and validation.
All lecture materials and example code are openly available on GitHub.
View on GitHub β