C
JavaScript/DOM/Lesson 14

イベント — *ユーザー入力への反応*

1時間·theory
このチャプター
2/2

イベント — *ユーザー入力への反応*

🎯 このレッスンを読み終えたら

このレッスンをすべて読み終えると、次の3つを自信を持ってできるようになります。

  • ✅ イベントキャプチャリング・バブリング・委譲 (delegation) パターン
  • ✅ addEventListener のクリーンアップ漏れによるメモリリーク
  • ✅ debounce / throttle の適用タイミング

学習目標をチェックリストとして持ち、すべて答えられるようになったらレッスンを閉じてください。

イベントシステム

核心を一言で

イベント = ユーザーが何かをしたときに発生するシグナル。クリック・キー入力・スクロール・マウス移動など。JSがこのシグナルを受け取って動作します。

よく使うイベント10種

イベントタイミング
clickクリック
dblclickダブルクリック
submitフォーム送信
input入力変更(リアルタイム)
change入力完了(フォーカスを失ったとき)
keydown / keyupキー押下・離放
focus / blurフォーカス取得・喪失
mouseover / mouseoutマウス進入・離脱
scrollスクロール
load / DOMContentLoadedページ読み込み完了

イベントリスナー

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

// 追加
const handler = (e) => console.log("クリック!");
btn.addEventListener('click', handler);

// 削除
btn.removeEventListener('click', handler);   // 同じ関数参照が必要

> 💡 インラインの onclick="..."古い書き方です。addEventListener が推奨 — 複数のハンドラーを登録でき、削除も可能。

イベントオブジェクト

javascript
input.addEventListener('input', (e) => {
    console.log(e.target.value);   // 現在の入力値
    console.log(e.type);            // 'input'
});

button.addEventListener('click', (e) => {
    console.log(e.target);          // クリックされた要素
    console.log(e.clientX, e.clientY);  // マウス座標
});

document.addEventListener('keydown', (e) => {
    console.log(e.key);             // 'Enter', 'Escape', 'a' など
    if (e.key === 'Enter') submit();
    if (e.ctrlKey && e.key === 's') save();   // ショートカットキー
});

デフォルト動作のキャンセル

javascript
form.addEventListener('submit', (e) => {
    e.preventDefault();   // ページのリロード防止
    // 直接処理
});

link.addEventListener('click', (e) => {
    e.preventDefault();   // リンク移動防止
    showModal();
});

イベントの伝播 — バブリング

html
<div id="parent">
    <button id="child">ボタン</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"));

// 🖱️ ボタン(child) クリック時出力順序:
//   "child"     ← ① クリックされた自分自身が最初
//   "parent"    ← ② 親へ "バブリング" される
//
// 💡 クリックは子から開始 → 親 → 祖先へと "上へ" 伝播する
//    これがバブリング。親も子のクリックを検知する。

イベントは子から親へと上方向に伝播します。これがバブリングです。

停止するには:

javascript
child.addEventListener('click', (e) => {
    e.stopPropagation();   // 親への伝播を停止
});

イベント委譲 — 効率的なパターン

html
<ul id="list">
    <li>アイテム 1</li>
    <li>アイテム 2</li>
    <li>アイテム 3</li>
    <!-- 1000個もっと -->
</ul>

<li> に1,000個のリスナー?非効率です。代わりに親に1つだけ:

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

バブリングのおかげで親が子のクリックを検知できます。メモリと性能の両方が向上し、動的に追加された子要素も自動的に処理されます。

debounce・throttle — 過剰な呼び出しを防ぐ

javascript
// 🐢 debounce — *最後の呼び出しからN ms間無入力になったとき*に実行
function debounce(fn, ms) {
    let timer;                                    // クロージャでtimerを記憶
    return (...args) => {
        clearTimeout(timer);                      // 以前の予約をキャンセル!
        timer = setTimeout(() => fn(...args), ms); // 新しくN ms後に予約
    };
}

// 使用例: 検索入力 — 300 msの間追加入力がないときのみfetch
const search = debounce((q) => {
    fetch(`/api/search?q=${q}`);
}, 300);

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

// 📊 効果:
//   ユーザーが "hello" と5文字入力 → inputイベントが5回発生
//   debounceがない場合 → fetchが5回呼び出される (無駄)
//   debounce 300ms → 最後の入力から300ms後 → fetchが1回

検索フォームでは入力が終わってから300ms後に1回だけAPIが呼ばれます。1,000回の入力 → 1回の呼び出し。

まとめ

  • addEventListener('type', handler) が標準
  • e.preventDefault() でデフォルト動作をブロック
  • e.stopPropagation() でバブリングをブロック
  • イベント委譲で動的な子要素を効率的に処理
  • debounce・throttle でパフォーマンスを最適化

⚡ 実際に試してみよう — イベントシステム (mock EventTarget)

ブラウザのsandbox制約により、*mock EventTarget* を使ってaddEventListener・dispatchEvent・preventDefault・バブリングをデモしています。
✏️ JS 코드
📟 コンソール出力
▶ 実行ボタンを押してください
⚠️ ブラウザのサンドボックスで実行 — console.log()のみ対応、alert/fetchは不可

🤖 AIにこう聞いてみましょう

このレッスンの概念を知ることで、AIに具体的に指示できるようになります。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。

  • 「このクリックイベントにdebounce 300msを適用して」
  • 「このイベントハンドラーにクリーンアップ (return () => removeEventListener) も追加して」

なぜトークンを節約できるのか

概念を知らないと、AIの回答を受け取った後に「それって何ですか?」と再度質問しなければなりません。その「再質問」がトークンを消費します。概念を一度理解しておけば、会話が一度で終わります

先に読むとよい概念: DOMとは?
次のおすすめ: イベントループ
イベント処理 - JavaScript