イベントリスナー削除(DOM) — el.removeEventListener('click', fn)
イベントを「もう受けたくない」瞬間は必ず来ます。removeEventListener は、追加済みのイベントリスナーを安全に外すための基本メソッド。正しく外すには「同じイベント名・同じ関数参照・同じオプション」で照合される点が重要です。
基本の使い方
<button id="buy">購入する</button>
<script>
const btn = document.getElementById("buy");
function onBuy() {
console.log("購入ボタンが押されました");
}
// 追加
btn.addEventListener("click", onBuy);
// 削除(同じイベント名・同じ関数参照)
btn.removeEventListener("click", onBuy);
</script>
HTML- 削除の照合条件: イベント名・関数参照・第3引数のオプションが追加時と一致している必要があります。
- 匿名関数は外せない: 追加時に無名関数を渡すと同じ参照を再指定できないため、外せません。変数や名前付き関数で参照を保持しましょう。
よく使うテンプレート集
条件を満たしたら自分で解除(1回だけ動かす)
function onOnce(e) {
console.log("一度だけ実行");
e.currentTarget.removeEventListener("click", onOnce);
}
el.addEventListener("click", onOnce);
JavaScript- ラベル: 一度だけ処理したいなら自前で解除。オプション
{ once: true }でも自動解除できます。
追加オプションを合わせて削除(capture など)
function onClick(e) { /* ... */ }
el.addEventListener("click", onClick, { capture: true });
// 削除時も capture: true を一致させる
el.removeEventListener("click", onClick, { capture: true });
JavaScript- ラベル: capture の有無が違うと同一リスナーと見なされず、削除できません。
画面遷移・Unmount 時のクリーンアップ
function onScroll() { /* ... */ }
window.addEventListener("scroll", onScroll);
function cleanup() {
window.removeEventListener("scroll", onScroll);
}
JavaScript- ラベル: 古い画面のリスナーが残ると、不要な処理やメモリリークの原因に。必ずクリーンアップ。
委譲(親)でまとめて受けて、不要になったら外す
const list = document.querySelector(".list");
function onListClick(e) {
const item = e.target.closest(".item");
if (!item) return;
console.log("clicked:", item.dataset.id);
}
list.addEventListener("click", onListClick);
// リスト全体のイベントを止めたいとき
list.removeEventListener("click", onListClick);
JavaScript- ラベル: 子を個別に外すより、親の一括委譲を止める方が管理が楽。
実務でのコツ
- 同じ関数参照を渡す: 後から外す予定なら、関数を変数に保持しておく。無名関数は避ける。
- オプション一致が必須:
capture/once/passiveは削除時にも一致させる。特にcaptureの違いで外れない事故が多い。 - 一度だけなら once を使う:
el.addEventListener("click", fn, { once: true })で自動解除。クリーンアップ漏れを防げます。 - AbortController でまとめて解除: 追加時に
signalを渡すと、controller.abort()一発で複数のリスナーを解除できます。
const ac = new AbortController();
el.addEventListener("click", fn, { signal: ac.signal });
el.addEventListener("mousemove", fn2, { signal: ac.signal });
// まとめて解除
ac.abort();
JavaScriptありがちなハマりポイントと対策
- 関数が違う参照になっている:
- 対策: 追加時と削除時で同じ関数オブジェクトを使う。必ず変数や名前付き関数で保持。
- capture を合わせていない:
- 対策: 追加時の
{ capture: true/false }を削除時も一致させる。
- 対策: 追加時の
- 匿名関数を追加してしまう:
- 対策: 解除前提なら無名関数は使わない。ラッパーを使うならラッパー自体を変数に保持。
- クリーンアップ漏れ:
- 対策: コンポーネント破棄やページ遷移フックで必ず解除。AbortControllerの活用も有効。
練習問題(手を動かして覚える)
<button id="a">A</button>
<button id="b">B</button>
<script>
const a = document.getElementById("a");
const b = document.getElementById("b");
// 1) 追加→3秒後に削除
function onA() { console.log("A clicked"); }
a.addEventListener("click", onA);
setTimeout(() => {
a.removeEventListener("click", onA);
console.log("Aのハンドラを解除");
}, 3000);
// 2) capture付きで追加→同じ設定で削除
function onB() { console.log("B capture"); }
b.addEventListener("click", onB, { capture: true });
setTimeout(() => b.removeEventListener("click", onB, { capture: true }), 2000);
// 3) once で自動解除
b.addEventListener("click", () => console.log("B once"), { once: true });
// 4) AbortController でまとめて解除
const ac = new AbortController();
function onMove(e) { /* ... */ }
document.addEventListener("mousemove", onMove, { signal: ac.signal });
setTimeout(() => ac.abort(), 4000); // 4秒後に解除
</script>
HTML直感的な指針
- 追加時と同じ「イベント名・関数参照・オプション」で外す。
- 解除前提のハンドラは必ず参照を保持、匿名関数は使わない。
- クリーンアップをルーチン化(Unmount/遷移/Abort)して漏れを防ぐ。
