Phase 3 · Browser DevelopmentModule 18~46 min read

Events & Interaction

Respond to users with event listeners, propagation, delegation, and keyboard-friendly interaction.

What you'll learn

Events turn browser input into application intent. Robust handlers understand propagation, delegate repeated controls, and preserve native keyboard behavior.

By the end of this lesson, you'll be able to:

  • Register and remove listeners
  • Explain capture and bubbling
  • Use delegation for dynamic interfaces

Core mental model

Use this decision table as a compact reference. Focus on what each tool means and when it earns its place in production code.

ConceptWhat it meansDecision rule
Event targetThe deepest node that initiated the eventUse closest() to resolve an intended control
Current targetThe node whose listener is runningUse for the delegated container
DelegationOne ancestor handles matching descendantsUse for repeated or dynamically added controls

Professional workflow

Build the behavior in small, observable steps. Each step should leave something you can inspect or test.

  1. Describe the event interaction boundary: inputs, outputs, state, timing, and expected failures.
  2. Implement the smallest correct path with names that expose intent.
  3. Add edge cases and failure handling before introducing abstractions.
  4. Verify behavior with realistic data and one deliberately adversarial example.
  5. Refactor only after the observable behavior is protected.

Make behavior observable

Before optimizing or abstracting, make inputs, outputs, state changes, timing, and failure paths visible. JavaScript becomes much easier to reason about when hidden work is exposed.

Guided code lab

Delegate a module list

One listener handles every current and future button.

delegation.js
const list = document.querySelector("#modules");
list.addEventListener("click", event => {
  const button = event.target.closest("button[data-module]");
  if (!button || !list.contains(button)) return;
  console.log("Open module", button.dataset.module);
});

Clean up with AbortController

One abort signal can remove a feature's listeners together.

cleanup.js
const controller = new AbortController();
window.addEventListener("resize", () => console.log("resized"), {
  signal: controller.signal,
});
document.querySelector("#close").addEventListener("click", () => {
  controller.abort();
}, { once: true });

Production practice

Contract

Keep the event interaction API explicit: accepted state, emitted events, DOM ownership, and cleanup.

Verification

Test with keyboard input, missing elements, repeated initialization, and teardown—not only a mouse happy path.

Operations

Measure user-visible latency and remove listeners, observers, object URLs, or media tracks when the feature ends.

Common failure mode

Calling stopPropagation everywhere hides coupling and breaks other features; delegate from clear ownership boundaries instead.

Independent workshop

Create an accessible accordion with delegated controls and removable listeners.

Your finished workshop must include:

  • Native buttons
  • aria-expanded synchronized with state
  • One delegated listener and a cleanup path

Definition of done

Demonstrate the happy path and at least two edge cases, keep responsibilities separated, and add a short note explaining one design choice.

Recap & quick check

Key takeaways

  • Events travel through capture and bubble phases
  • target and currentTarget differ
  • Delegation scales repeated controls
  • Listeners need lifecycle cleanup

Quick check

1. Which property is the original deepest node?

2. What does once: true do?

3. Why use closest() in delegation?

Keep the workshop. Later modules deliberately build on these decisions, so each exercise can become part of your final portfolio architecture.