YASD-TECH
YASD TECH
# JavaScript

addEventListener の引数(type・listener・options)

投稿日:2026/8/6

更新日:2026/8/6

ttitleImage

addEventListener の引数(type・listener・options)

addEventListener は「このできごとが起きたら、この関数を呼んで」をブラウザに登録するメソッド。
引数は 3 つしかないのに、第 3 引数まわり(once / passive / capture / signal)で毎回迷うので整理しておく。

結論

  • target.addEventListener(type, listener, options) の 3 つ。typeon を付けない文字列('click'
  • 第 3 引数はオブジェクト(options)を渡すのが今の書き方。旧来の true / falsecapture だけを指定した形
  • removeEventListener で消せるのは「同じ関数参照 + 同じ capture」のときだけ。無名関数は消せないので oncesignal を使う
  • passive: true は「preventDefault() しません」というブラウザへの約束。スクロールが軽くなる代わりに preventDefault() は無視される

基本の形

javascript
target.addEventListener(type, listener, options);
// 旧来の書き方(第3引数が真偽値)
target.addEventListener(type, listener, useCapture);
javascript
const button = document.querySelector('#submit');

button.addEventListener('click', (event) => {
  console.log('クリックされました', event.target);
});
引数 必須 中身
type 文字列 イベント名。'click' / 'keydown' / 'submit'
listener 関数 または オブジェクト 発火時に呼ばれる処理
options オブジェクト または 真偽値 発火タイミングと後片付けの制御

第1引数:type(イベント名)

監視したいイベントの種類を文字列で指定する。onclick のような on は付けない

分類
ユーザー操作 click / keydown / input / submit / mousemove
画面・ブラウザの変化 scroll / resize / DOMContentLoaded / visibilitychange
メディア・通信 play / ended / online / offline
独自イベント CustomEvent で自作した任意の名前(後述)

タイプミスしてもエラーにならない。'clik' と書いても登録は成功し、ただ永遠に発火しないだけなので、動かないときはまずここを疑う。


第2引数:listener(呼ばれる処理)

関数を渡す

イベントオブジェクトが第1引数として自動で渡される

javascript
form.addEventListener('submit', (event) => {
  event.preventDefault();       // デフォルト動作(ページ遷移)を止める
  console.log(event.type);      // 'submit'
  console.log(event.target);    // 実際にイベントが起きた要素
  console.log(event.currentTarget); // リスナーを登録した要素(= form)
});

targetcurrentTarget は別物。子要素をクリックしたとき target は子、currentTarget は登録した親になる。
イベント委譲(親でまとめて受けて event.target で判定する)はこの差を使っている。

handleEvent を持つオブジェクトを渡す

関数以外に、handleEvent メソッドを持つオブジェクトも渡せる。状態を持たせたいときに使う。

javascript
const counter = {
  count: 0,
  handleEvent(event) {
    this.count += 1;                 // this はこのオブジェクト
    console.log(`${this.count} 回目`);
  },
};

button.addEventListener('click', counter);

this が何を指すかは渡し方で変わる。

渡し方 リスナー内の this
function event.currentTarget(登録した要素)
アロー関数 定義した場所の this(要素ではない)
handleEvent を持つオブジェクト そのオブジェクト自身

第3引数:options / useCapture

オプションオブジェクト

プロパティ デフォルト 説明
once Boolean false true で 1 度実行された後に自動でリスナーが外れる
passive Boolean false(※例外あり) true で「preventDefault() しない」と宣言し、スクロール性能を上げる
capture Boolean false true でキャプチャフェーズ(親 → 子)で実行する
signal AbortSignal AbortController の signal。外から一括で解除できる

真偽値(useCapture)

第 3 引数に真偽値を直接渡す旧来の書き方。capture だけを指定した形と等価。

javascript
el.addEventListener('click', fn, true);
el.addEventListener('click', fn, { capture: true });  // 同じ意味

キャプチャとバブリング

イベントは window から降りてきて(キャプチャ)、ターゲットに到達し、また上へ戻る(バブリング)capture はこのどちらで拾うかの指定。

flowchart TD
    W1["window"] --> D1["document"]
    D1 --> P1["div#parent"]
    P1 --> T["button#child<br/>(ターゲット)"]
    T --> P2["div#parent"]
    P2 --> D2["document"]
    D2 --> W2["window"]

    C["キャプチャフェーズ<br/>capture: true"] -.- W1
    B["バブリングフェーズ<br/>capture: false(デフォルト)"] -.- W2
javascript
parent.addEventListener('click', () => console.log('親:キャプチャ'), { capture: true });
parent.addEventListener('click', () => console.log('親:バブリング'));
child.addEventListener('click', () => console.log('子'));

// child をクリックしたときの出力
// 親:キャプチャ → 子 → 親:バブリング

ターゲット自身に登録したリスナーは、capture の値によらず登録した順に呼ばれる(ターゲットフェーズ)。
順序が効いてくるのは祖先要素に登録したリスナーだけ。

途中で止めたいときは event.stopPropagation()(以降の伝播を止める)。同じ要素に付いた他のリスナーまで止めるなら event.stopImmediatePropagation()


リスナーを解除する 3 パターン

方法 向いている場面 注意
removeEventListener 任意のタイミングで個別に外す 同じ関数参照同じ capture が必要
{ once: true } 1 回きりの処理(初回クリック、読み込み完了) 発火しなければ残り続ける
{ signal } 複数のリスナーをまとめて外す(SPA の後片付け) AbortController を保持しておく必要がある
javascript
// ① removeEventListener:関数を変数に入れておく
const onScroll = () => console.log('スクロール中');
window.addEventListener('scroll', onScroll);
window.removeEventListener('scroll', onScroll);

// ② once:1度実行されたら自動で外れる
button.addEventListener('click', () => {
  console.log('最初の一回だけ実行されます');
}, { once: true });

// ③ signal:まとめて解除
const controller = new AbortController();
const { signal } = controller;

window.addEventListener('resize', onResize, { signal });
window.addEventListener('scroll', onScroll, { signal });
document.addEventListener('keydown', onKeydown, { signal });

controller.abort();  // 上の3つが一度に外れる

signal の後片付けはフレームワークのクリーンアップと相性がよい。

javascript
// React の useEffect での例
useEffect(() => {
  const controller = new AbortController();

  window.addEventListener('resize', handleResize, { signal: controller.signal });
  window.addEventListener('orientationchange', handleResize, { signal: controller.signal });

  return () => controller.abort();  // アンマウント時にまとめて解除
}, []);

ハマりどころ

① 無名関数は removeEventListener で外せない

javascript
el.addEventListener('click', () => console.log('hi'));
el.removeEventListener('click', () => console.log('hi'));  // 外れない

見た目が同じでも別の関数オブジェクトなので一致しない。.bind(this)() => fn() でラップした場合も同様に毎回新しい関数になる。変数に入れておくか、once / signal を使う。

capture が一致しないと外れない

javascript
el.addEventListener('click', fn, { capture: true });
el.removeEventListener('click', fn);                  // 外れない(capture が false 扱い)
el.removeEventListener('click', fn, { capture: true }); // 外れる

照合に使われるのは type / 関数参照 / capture の 3 つだけ。oncepassive は照合に関係しない。

passive のデフォルトは常に false ではない

window / document / document.body に登録した touchstart touchmove wheel などのスクロール系イベントは、明示しなければ passive: true として扱われる(スクロールのカクつきを防ぐためのブラウザ側の仕様)。

javascript
// これは preventDefault() が効かず、コンソールに警告が出る
window.addEventListener('touchmove', (e) => e.preventDefault());

// スクロールを止めたいなら明示する
window.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false });

逆に、スクロール中に重い処理をしていないのに描画が重いときは { passive: true } を明示すると改善することがある。

④ 同じリスナーの二重登録は無視される

type / 関数参照 / capture がすべて同じ登録は、2 回目以降が捨てられる。

javascript
el.addEventListener('click', fn);
el.addEventListener('click', fn);  // 無視される → 1回しか呼ばれない

el.addEventListener('click', fn, { capture: true });  // capture が違うので別枠で登録される

「重複登録で 2 回発火した」ときは、たいてい毎回違う無名関数を登録している(コンポーネントの再描画のたびに登録するなど)。

⑤ バブリングしないイベントがある

focus / blur / スクロール要素の scroll などは親に伝播しない。イベント委譲で拾えないので、バブリングする代替イベント(focusin / focusout)を使う。

DOMContentLoaded は登録が遅いと二度と来ない

<script> の実行タイミングによっては、登録した時点で既に発火済みのことがある。その場合は document.readyState で判定する。

javascript
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
  init();
}

カスタムイベント

ブラウザ標準のイベントだけでなく、CustomEvent で自作したイベントも同じ仕組みで受け取れる。任意のデータは detail に載せる。

javascript
// 待ち受ける
document.addEventListener('cartUpdated', (event) => {
  console.log('カートが更新されました:', event.detail);  // { itemId: 123, quantity: 2 }
});

// 好きなタイミングで発火させる
document.dispatchEvent(new CustomEvent('cartUpdated', {
  detail: { itemId: 123, quantity: 2 },
  bubbles: true,   // 親へ伝播させたいなら明示(デフォルトは false)
}));
sequenceDiagram
    participant Code as 発火側のコード
    participant Target as EventTarget<br/>(document / 要素)
    participant Listener as 登録済みリスナー

    Listener->>Target: addEventListener('cartUpdated', fn)
    Note over Target: リスナーを保持
    Code->>Target: dispatchEvent(new CustomEvent(...))
    Target->>Listener: fn(event) を実行
    Note over Listener: event.detail でデータを受け取る

標準イベントでも自作イベントでも、「特定のできごとが起きたら、登録しておいた関数を実行する」という役割は変わらない。


参考

Index

  • addEventListener の引数(type・listener・options)
  • 結論
  • 基本の形
  • 第1引数:type(イベント名)
  • 第2引数:listener(呼ばれる処理)
  • 関数を渡す
  • handleEvent を持つオブジェクトを渡す
  • 第3引数:options / useCapture
  • オプションオブジェクト
  • 真偽値(useCapture)
  • キャプチャとバブリング
  • リスナーを解除する 3 パターン
  • ハマりどころ
  • ① 無名関数は removeEventListener で外せない
  • ② capture が一致しないと外れない
  • ③ passive のデフォルトは常に false ではない
  • ④ 同じリスナーの二重登録は無視される
  • ⑤ バブリングしないイベントがある
  • ⑥ DOMContentLoaded は登録が遅いと二度と来ない
  • カスタムイベント
  • 参考