C
JavaScript/DOM/Lesson 14

Events — *Responding to User Input*

1 hr·theory
This chapter
2/2

Events — *Responding to User Input*

🎯 By the end of this lesson

After reading this lesson, you will be able to confidently do the following 3 things.

  • ✅ Event capturing, bubbling, and delegation patterns
  • ✅ Memory leaks from missing addEventListener cleanup
  • ✅ When to apply debounce / throttle

Keep these learning goals as a checklist — once you can answer all of them, close the lesson.

The Event System

The Core in One Line

Events = signals that fire when the user does something. Clicks, key presses, scrolls, mouse movements, etc. JS receives these signals and acts.

10 Commonly Used Events

EventWhen
clickClick
dblclickDouble-click
submitForm submission
inputInput change (real-time)
changeInput complete (on blur)
keydown / keyupKey press / release
focus / blurFocus gained / lost
mouseover / mouseoutMouse enter / leave
scrollScroll
load / DOMContentLoadedPage load complete

Event Listeners

javascript
const btn = document.querySelector('#submit');

// Add
const handler = (e) => console.log("Click!");
btn.addEventListener('click', handler);

// Remove
btn.removeEventListener('click', handler);   // Same function reference needed

> 💡 Inline onclick="..." is the old way. addEventListener is recommended — supports multiple handlers and removal.

The Event Object

javascript
input.addEventListener('input', (e) => {
    console.log(e.target.value);   // Current input value
    console.log(e.type);            // 'input'
});

button.addEventListener('click', (e) => {
    console.log(e.target);          // Clicked element
    console.log(e.clientX, e.clientY);  // Mouse coordinates
});

document.addEventListener('keydown', (e) => {
    console.log(e.key);             // 'Enter', 'Escape', 'a', etc.
    if (e.key === 'Enter') submit();
    if (e.ctrlKey && e.key === 's') save();   // Shortcut key
});

Preventing Default Behavior

javascript
form.addEventListener('submit', (e) => {
    e.preventDefault();   // Prevent page refresh
    // Handle directly
});

link.addEventListener('click', (e) => {
    e.preventDefault();   // Prevent link navigation
    showModal();
});

Event Propagation — Bubbling

html
<div id="parent">
    <button id="child">Button</button>
</div>
javascript
const parent = document.querySelector('#parent');
const child  = document.querySelector('#child');

parent.addEventListener('click', () => console.log("parent"));
child .addEventListener('click', () => console.log("child"));

// 🖱️ When button (child) is clicked, output order:
//   "child"     ← ① First, the clicked element itself
//   "parent"    ← ② "Bubbles" up to the parent
//
// 💡 Clicks start at the child → propagate "up" to parent → ancestor.
//    This is bubbling. The parent also hears the child's click.

Events propagate upward from child to parent. This is called bubbling.

Stopping propagation:

javascript
child.addEventListener('click', (e) => {
    e.stopPropagation();   // No propagation to parent
});

Event Delegation — An Efficient Pattern

html
<ul id="list">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <!-- 1000 more -->
</ul>

1,000 listeners on each <li>? Inefficient. Instead, one listener on the parent:

javascript
document.querySelector('#list').addEventListener('click', (e) => {
    if (e.target.tagName === 'LI') {
        console.log(e.target.textContent);
    }
});

Thanks to bubbling, the parent detects clicks on its children. Better memory and performance. Dynamically added children are handled automatically too.

debounce · throttle — Preventing Excessive Calls

javascript
// 🐢 debounce — runs *only after N ms of silence following the last call*
function debounce(fn, ms) {
    let timer;                                    // Remember timer with closure
    return (...args) => {
        clearTimeout(timer);                      // Cancel previous reservation!
        timer = setTimeout(() => fn(...args), ms); // New reservation after N ms
    };
}

// Usage example: search input — fetch only when no additional input for 300 ms
const search = debounce((q) => {
    fetch(`/api/search?q=${q}`);
}, 300);

const input = document.querySelector('#search');
input.addEventListener('input', e => search(e.target.value));

// 📊 Effect:
//   User types "hello" (5 characters) → 5 input events occur
//   Without debounce → fetch called 5 times (wasteful)
//   With debounce 300ms → 300ms after last input → fetch 1 time

In a search field, the API is called only once, 300ms after typing ends. 1,000 keystrokes → 1 API call.

Summary

  • addEventListener('type', handler) is the standard
  • e.preventDefault() blocks default browser behavior
  • e.stopPropagation() stops bubbling
  • Event delegation handles dynamic children efficiently
  • debounce · throttle optimize performance

⚡ Try It Yourself — Event System (mock EventTarget)

Due to browser sandbox restrictions, this demo uses a *mock EventTarget* to illustrate addEventListener, dispatchEvent, preventDefault, and bubbling.
✏️ JS 코드
📟 Console output
▶ Press the Run button
⚠️ Runs in a browser sandbox — only console.log() is supported; alert/fetch are not.

🤖 Try Asking AI This

Knowing the concepts in this lesson lets you give AI specific instructions. Instead of a vague 'fix this,' you can make vocabulary-driven requests — and that's where token savings begin.

  • "Apply debounce 300ms to this click event"
  • "Add cleanup to this event handler (return () => removeEventListener)"

Why This Saves Tokens

When you don't know the concepts, you have to follow up with 'What does that mean?' after every AI response. Those follow-up questions eat tokens. Learn the concept once, and the conversation finishes in one round.

Read this first: What is DOM?
Up next: Event Loop
Event Handling - JavaScript