JavaScript | DOM 操作:イベント基礎 – removeEventListener

JavaScript
スポンサーリンク

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 で自動解除を活用し、委譲で付け外し自体を減らす。これらを守れば、初心者でも壊れにくく滑らかなイベント管理が書けます。

タイトルとURLをコピーしました