removeEventListener とは何か
removeEventListener は、addEventListener で登録したイベントリスナーを「取り外す」ためのメソッドです。特定の要素(または window, document 等)に対して、イベント名と同じ関数参照(そして同じキャプチャ設定)を渡すことで、以降そのイベントは反応しなくなります。ここが重要です:取り外すには「同じ関数参照」と「同じ capture 設定」を再指定する必要があります。関数が違う、capture の真偽が違うと外れません。
基本の使い方(同じ関数参照とキャプチャ設定をそろえる)
名前付き関数で登録・解除する
<button id="buy">購入</button>
<script>
const btn = document.getElementById("buy");
function onClick() {
btn.disabled = true;
btn.textContent = "購入済み";
}
btn.addEventListener("click", onClick);
// もう不要になったら外す
btn.removeEventListener("click", onClick);
</script>
HTMLここが重要です:無名関数(() => {} をその場で渡す)だと“同じ参照”を作れないため解除できません。後で外す予定のリスナーは、必ず名前付き関数か、変数に保持した関数を使います。
capture 設定を合わせる(第三引数またはオプション)
<div id="box">クリック</div>
<script>
const box = document.getElementById("box");
function log(e) { console.log("捕捉"); }
box.addEventListener("click", log, { capture: true });
// 解除時も capture: true を合わせる
box.removeEventListener("click", log, { capture: true });
</script>
HTMLここが重要です:解除時に一致が必要なのは「capture(または第3引数の true/false)」だけです。passive や once は一致していなくても構いません(once はそもそも一度で自動解除されます)。
なぜ取り外すのか(性能・バグ・メモリの観点)
二重反応・積み重ねを防ぐ
同じ箇所で addEventListener を何度も呼ぶと、クリック1回で処理が複数回走るなどのバグにつながります。ここが重要です:初期化のたびに「前のリスナーを外してから付け直す」か、付与済みかどうかのフラグ管理を徹底します。
リソース節約と体感の維持
スクロールや入力のリスナーは高頻度に呼ばれます。不要な場面では外すことで CPU 使用率や電力消費を抑え、体感のカクつきを防げます。ここが重要です:重い処理は必要な時だけリスナーを有効化する設計にします。
メモリリークの予防
DOM を削除しても、外部で関数参照を持ち続けていると、不要なデータが解放されにくくなります。ここが重要です:コンポーネントの破棄やモーダルのクローズ時に「リスナー解除+参照破棄」をセットで行う癖をつけます。
実践の型(開始・停止の切り替え、クリーンアップ、一次だけ)
一時的に有効化して、終わったらすぐ外す
<button id="start">計測開始</button>
<script>
const start = document.getElementById("start");
function onMove(e) { /* 軽い処理 */ }
start.addEventListener("click", () => {
window.addEventListener("mousemove", onMove);
setTimeout(() => {
window.removeEventListener("mousemove", onMove); // 10秒後に停止
}, 10000);
});
</script>
HTMLここが重要です:開始と終了を「同じ関数参照」で対にする。長時間の常駐リスナーは、必要な時だけ有効化する設計が効きます。
モーダルのオープン/クローズでイベントを管理する
<button id="open">開く</button>
<div id="modal" hidden>...</div>
<script>
const open = document.getElementById("open");
const modal = document.getElementById("modal");
function onKey(e) {
if (e.key === "Escape") closeModal();
}
function openModal() {
modal.hidden = false;
document.addEventListener("keydown", onKey);
}
function closeModal() {
modal.hidden = true;
document.removeEventListener("keydown", onKey);
}
open.addEventListener("click", openModal);
</script>
HTMLここが重要です:リスナーの寿命を UI の寿命に合わせる。開閉に伴う登録・解除を“同じ場所”で管理すると漏れが減ります。
once や AbortController で“自動解除”を利用する
<button id="once">一回だけ</button>
<script>
const btn = document.getElementById("once");
btn.addEventListener("click", () => console.log("最初の一回"), { once: true });
</script>
HTML<button id="watch">監視</button>
<script>
const ctrl = new AbortController();
const { signal } = ctrl;
document.addEventListener("scroll", () => {}, { signal }); // ctrl.abort() で一括解除
// 必要なくなったら
ctrl.abort();
</script>
HTMLここが重要です:手動の removeEventListener を忘れがちな場面では、once や AbortController の“自動的に外れる仕組み”が有効です。設計の負担が減ります。
よくある落とし穴と安全策(無名関数・オプション不一致・委譲)
無名関数は外せない
解除が必要なものに無名関数を渡さない。関数を変数に保持して使い回すのが基本です。ここが重要です:「同じ参照」を再指定できるように、名前付き関数か const 関数を使います。
capture が一致しないと外れない
追加時に { capture: true } で付けたら、解除時にも { capture: true } を指定します。ここが重要です:第三引数の true/false でも OK。passive や once は一致していなくても解除されます。
委譲で“そもそも大量の付け外しを避ける”
多数の子要素に個別のリスナーを付ける代わりに、親でまとめて受け取る「イベント委譲」を使うと、付け外しの数自体が減ります。ここが重要です:委譲+closest で対象を絞る設計にすると、管理が楽になり性能も安定します。
<ul id="items"></ul>
<script>
const list = document.getElementById("items");
list.addEventListener("click", (e) => {
const del = e.target.closest(".delete");
if (!del) return;
del.closest("li")?.remove();
});
</script>
HTML実践例(タイマー併用、スクロールの間引き、ページ遷移でのクリーンアップ)
一時的な状態中だけクリックを受け付ける
<button id="send">送信</button>
<script>
const send = document.getElementById("send");
function onClick() {
send.disabled = true;
send.textContent = "送信中…";
setTimeout(() => {
send.disabled = false;
send.textContent = "送信";
send.addEventListener("click", onClick); // 再度有効化
}, 800);
send.removeEventListener("click", onClick); // 二重クリック防止
}
send.addEventListener("click", onClick);
</script>
HTMLここが重要です:処理中はリスナーを外して“二重反応”を防ぎ、終了時に再登録する。UI 状態とリスナーの寿命を揃えると安定します。
スクロール監視を必要時だけオンにする
<script>
let watching = false;
function onScroll() { /* 軽い処理 */ }
function startWatch() {
if (watching) return;
watching = true;
window.addEventListener("scroll", onScroll, { passive: true });
}
function stopWatch() {
if (!watching) return;
watching = false;
window.removeEventListener("scroll", onScroll, { passive: true }); // passive は一致不要
}
</script>
HTMLここが重要です:フラグで重複登録を防ぎ、不要なときは外す。passive: true を併用すると滑らかさが保てます。
SPA のページ遷移でクリーンアップ
<script>
const handlers = [];
function mountPage() {
const h = (e) => { /* ページ固有処理 */ };
document.addEventListener("keydown", h);
handlers.push(["keydown", document, h]);
}
function unmountPage() {
for (const [type, target, fn] of handlers) {
target.removeEventListener(type, fn);
}
handlers.length = 0;
}
</script>
HTMLここが重要です:どのイベントをどこに付けたかを配列で管理し、アンマウント時に総撤去する。抜け漏れのない“後片付け”の型です。
まとめ
removeEventListener は「登録済みのイベントリスナーを外す」ための基本メソッドです。解除には同じ関数参照と同じ capture 設定が必要。二重反応の防止、性能の維持、メモリリークの予防のために、開始と終了を対で設計し、開閉・マウント/アンマウントのタイミングで確実にクリーンアップする。忘れがちな場面は once や AbortController で自動解除を活用し、委譲で付け外し自体を減らす。これらを守れば、初心者でも壊れにくく滑らかなイベント管理が書けます。
