← Back to JavaScript series
πŸ–±οΈ
DOM
Intermediate Β· Prerequisite: 18_DOM_Manipulation

19. Event Handling

Removing a listener requires the same function reference you registered with. Anonymous functions can't be removed.

eventsclickbubblingdelegation
Duration
⏱ ~1 hour
Level
πŸ“Š Intermediate
Prerequisite
🎯 Lecture 18
OUTCOME
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

javascript
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.

javascript
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

FileWhat it covers
index.htmlExample HTML
01_addeventlistener.jsRegister and remove a listener
02_event_object.jsReading the event object
03_prevent_default.jsUsing preventDefault
04_delegation.jsEvent delegation

src/01_addeventlistener.js

javascript
/**
 * 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

javascript
/**
 * 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

javascript
/**
 * 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

javascript
/**
 * 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

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

  1. Using `onclick = fn` and being stuck with only one handler.
  2. Registering an anonymous function, then trying to remove it.
  3. Confusing `event.target` with `event.currentTarget`.
  4. Forgetting `preventDefault` on form submit and getting a page reload.
  5. 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

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

javascript
/**
 * 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.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—