イベントの伝播とイベント委譲を理解しよう
更新日:2026-08-02
子要素で発生したイベントは親要素へ伝わります。この仕組みを利用すると、親へ一つのイベントを登録するだけで複数要素を扱えます。
今回は、記事一覧の「詳細」と「削除」を一つの親要素で受け取ります。イベントがどこからどこへ伝わるかをConsoleで確認してから、イベント委譲を実装します。
今回使用するファイル構成
javascript-practice/
├── index.html
└── js/
└── script.js
イベント委譲を実装する流れ
バブリングを確認する
<div id="article-list">
<button type="button" data-id="1"><span>記事を開く</span></button>
</div>
const articleList = document.querySelector('#article-list');
articleList.addEventListener('click', (event) => {
console.log(event.target);
console.log(event.currentTarget);
});
targetは実際にクリックされた要素、currentTargetはイベントを登録した要素です。spanをクリックすると両者は異なります。
closestで操作対象を探す
articleList.addEventListener('click', (event) => {
const button = event.target.closest('button[data-id]');
if (!button || !articleList.contains(button)) {
return;
}
console.log(button.dataset.id);
});
closest()は自分自身から親方向へ条件に合う要素を探します。対象外なら早期リターンします。
動的に追加した要素にも対応する
イベントは親へ登録済みなので、後からappend()したボタンのクリックも受け取れます。これをイベント委譲と呼びます。
stopPropagation()で伝播を止める方法もありますが、多用すると外側の処理が動かない原因になります。必要な理由が明確な場合だけ使います。
複数種類の操作を親要素で受け取る
操作の種類をdata属性へ記述する
<ul id="article-list">
<li data-id="38">
<span>JavaScriptの歴史</span>
<button type="button" data-action="open"><span>詳細</span></button>
<button type="button" data-action="remove"><span>削除</span></button>
</li>
<li data-id="39">
<span>JavaScriptを動かす</span>
<button type="button" data-action="open"><span>詳細</span></button>
<button type="button" data-action="remove"><span>削除</span></button>
</li>
</ul>
<p id="action-message" aria-live="polite"></p>
ボタン内のspanをクリックしても操作できるよう、event.targetそのものではなくclosest()でボタンを探します。
actionごとに処理を分ける
const articleList = document.querySelector('#article-list');
const actionMessage = document.querySelector('#action-message');
articleList.addEventListener('click', (event) => {
const button = event.target.closest('button[data-action]');
if (!button || !articleList.contains(button)) {
return;
}
const item = button.closest('li[data-id]');
const articleId = Number(item.dataset.id);
const action = button.dataset.action;
if (action === 'open') {
actionMessage.textContent = `記事ID ${articleId}の詳細を開きます。`;
return;
}
if (action === 'remove') {
item.remove();
actionMessage.textContent = `記事ID ${articleId}を削除しました。`;
}
});
イベントリスナーは一覧全体に一つだけです。ボタンが100個あっても、各ボタンへ個別登録する必要がありません。
後から追加した要素で確認する
const newItem = document.createElement('li');
newItem.dataset.id = '40';
newItem.innerHTML = `
<span>変数とデータ型</span>
<button type="button" data-action="open">詳細</button>
<button type="button" data-action="remove">削除</button>
`;
articleList.append(newItem);
この例のHTML文字列は開発者が固定で記述しています。利用者の入力をinnerHTMLへ埋め込んではいけません。
追加後のボタンにも、親に登録済みのイベントが伝わります。これが、動的な一覧でイベント委譲が役立つ理由です。
伝播を調査する方法
Consoleへevent.target、event.currentTarget、buttonを順番に表示します。どの要素をクリックしたか、どこでイベントを受けたか、最終的にどの操作要素を採用したかを区別できます。
イベントが二重に動く場合は、子と親の両方へ同じ目的のリスナーを登録していないか確認します。すぐにstopPropagation()を追加するのではなく、登録場所を整理してください。
まとめ
イベント委譲では、親でイベントを受け取り、targetとclosest()から操作対象を特定します。次の記事ではブラウザへ設定値を保存します。