addEventListener の第三引数の capture

note

once くらいしか使う機会がなかったが capture が便利なので改めて調べてみた。

第三引数は options のオブジェクトでの指定と useCapture の論理値での指定の 2 パターンがある。
useCaptureoptionscapture とほぼ同義なので基本的には options 指定で良い。
そもそも第三引数自体が optional なので指定しなくても良い。胡乱だけど IE だと必須だった気がするが、 IE なので気にしなくて良い。

options のパラメータには capture, once, passive, signal が指定できる。

capture は DOM に登録した listener が発火する順番を指定できる。
true の場合は(他に指定がなければ)最速で発火する。
例えば全体で参照できる場所へ

window.addEventListener('DOMContentLoaded', () => {
  const anchors = document.querySelectorAll<HTMLAnchorElement>('a[href*="#"]');

  anchors.forEach(anchor => {
    anchor.addEventListener('click', (event) => {
      event.preventDefault();

      ...
    });
  });
});

みたいな処理を書いたとする。
ハッシュを指定している全ての HTMLAnchorElement を指定して処理を行う。
ページ内リンクとかでよく使うやつだ。

一方で、タブコンテンツ等を実装する場合にタブの部分をアンカーリンクにする場合がある。

window.addEventListener('DOMContentLoaded', () => {
  const tabs = document.querySelector<HTMLAnchorElement>('a.tab');

  anchors.forEach(anchor => {
    anchor.addEventListener('click', (event) => {
      event.preventDefault();

      ...
    });
  });
});

セレクタが同じだけで同じコードだが。

どちらも href へハッシュを指定している場合、タブをクリックした際に前者の処理が発生する。
event.stopPropagation() を使えばイベントの伝播を止められるが、前者が先に発火している場合は止められない。
その際に

...

  anchors.forEach(anchor => {
    anchor.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();

      ...
    }, {
      capture: true,
    });
  });
});

とすることで listener を最速で発火させた上で前者のイベントへの伝播を止めることができる。

ただし、乱用すると管理が大変になりそうなので、汎用的な処理であってもセレクタで範囲を絞って listener を登録するべきだと思う。