【JavaScript】イベントリスナーの引数(e)~トラブル回避の備忘録~

初めまして!

昨年11月にアルバイトとして入社しました、ナカムーです!

エンジニア

ナカムー

大学では哲学を専攻してきたのですが、そこでは「我(私)とは何か」について考える思想にしばしば出会いました。

エンジニア

ナカムー

そういった意味ではイベントリスナー第一引数の(e)って、自分自身のことを良く知っている存在かもしれないなぁと(?)…。

エンジニア

ナカムー

謎ボヤキ失礼いたしました、本題に入ります。

JavaScriptを活用していく上で避けては通れない「イベントリスナー」

ユーザーのあらゆる動作をきっかけに処理を行えるのは非常に便利なのですが、

時にこの利便性はトラブルの元にもなり得ます。

本記事ではそのようなトラブルを未然に防ぐ、

ないしはトラブル発生時の対処をスムーズにするために知っておきたい、

イベントリスナーの第一引数「e」にまつわる2つのプロパティと3つのメソッドをご紹介します。

紹介するメソッド・プロパティ

  • target
  • currentTarget
  • preventDefault()
  • stopPropagation()
  • stopImmediatePropagation()

イベントリスナーのつまづきポイント

addEventListenerで登録したイベントリスナーの第一引数には、

そのイベント自身のあらゆる情報(何の要素に・何をきっかけにetc…)が盛り込まれた

「イベントオブジェクト」なるものが代入されます。

ここに慣例として「e」や「event」等の名前で引数を設定し、

それに対して各プロパティやメソッドを適用すると、

当該イベントそのものに関する処理を実装できるといった仕組みです。

ここで問題となるつまづきポイントは、以下の2点です。

  • targetプロパティは、必ずしも処理を登録した要素のみを示すわけではない
  • イベントは伝播する

これらを理解し適切な対処を行わなければ、

全く意図していない別の要素のイベントが発火する等、

意図した挙動にならない場合があります。

targetとcurrentTarget

targetプロパティとcurrentTargetプロパティは、ざっくりと言えば

イベントリスナーを登録する要素それ自体に何かしらの処理を行いたい場合

に使用します。

target

targetプロパティが指し示すのは、

イベントが発生した要素です。

例えば、

const hoge = document.querySelector(".hoge");

hoge.addEventListener("click", e => {
  e.target.style.backgroundColor = "#ff0000";
});

とすれば、

hogeクラスのついた要素をクリックすると、hogeクラスのついた要素の背景色が変化(赤)する」

ことが分かるかと思います。

しかし問題はここからで、まず前提として、

親要素に登録したイベントリスナーは子要素に発生したイベントでも発火します。

例えば以下のようなHTMLだった場合

<div class="hoge">
  <p class="huga">abcde</p>
  <div class="foo">
    <p class="bar">12345</p>
  </div>
</div>

この場合、「huga」だけでなく「foo」「bar」の要素をクリックした際にも、上記のイベントが発火します。

そしてその際にも、targetはあくまでイベントが発生した要素を示します。

すると起こるのは、

親要素hogeの背景色を変化させるためにhogeのみに明示的に登録したはずの背景色変更処理が、その子要素にも適用される

という一見「?」な事態です。

もちろんそれが意図した挙動であれば良いのですが、

「子要素はあくまでも親要素の一部であり、

子要素をクリックしてもそれは全体を包む親要素へのクリックとみなす

つまりここで変化させるのは親要素であるといった挙動を期待することが往々にしてあります。

それを実現するのが、次のcurrentTargetプロパティです。

currentTarget

currentTargetプロパティが示すのは、まさに

イベントを登録した対象の要素

です。

<div class="hoge">
  <p class="huga">abcde</p>
  <div class="foo">
    <p class="bar">12345</p>
  </div>
</div>
document.querySelector(".hoge").addEventListener("click", e => {
  e.currentTarget.style.backgroundColor = "#ff0000";
});

とすれば、hogeが内包するどの子要素をクリックしたとしても、常にhogeの背景色のみが変化します。

targetに比べ、素直にaddEventListenerの直前に記述した要素のみを対象として処理を行えるので、

何か特別な意図が無い限りはcurrentTargetを使っておけば間違いありません。

イベント伝播を制御するメソッド

targetプロパティの解説においても触れましたが、

親要素のイベントリスナーは子要素に発生したイベントにも反応し、発火します。

ここで起こっているのはイベントの伝播(バブリング)というもので、

いわば「子要素に発生したイベントがその親要素を順々に伝っていく現象」と捉えて差し支えありません。

これがあるからこそ「子要素は親要素の一部」であることが出来るのですが、

例えば親要素に特有のクリックイベント・子要素に特有のクリックイベントを同時に実装したい場合等、

時にはこの仕様が邪魔になることもあります。

そこで用いるのが、以下のメソッドです。

  • preventDefault()
  • stopPropagation()
  • stopImmediatePropagation()

ちなみに、

jQueryにおけるreturn false → preventDefault()とstopPropagation()を併用した状態

ネイティブJavaScriptにおけるreturn false → preventDefault()を実行した状態

となります!

preventDefault()

preventDefault()メソッドは、

イベントリスナーの対象となる要素のデフォルトの動作を無効化します。

例えば<a>タグの場合、要素をクリックするとhref属性に従ってページ遷移処理が行われますが、

当該要素のイベントリスナー内でこのメソッドを記述しておけばページ遷移を無効化できます。

単純にデフォルトの動作が不要な際に用いれば良いのですが、

例えば特定の条件下ではページ遷移させたくない場合等にも、

if文を併用する形で活用すると便利です↓。

document.querySelector(".hoge").addEventListener("click", e => {
  if(e.currentTarget.classList.contains("inactive")) {
    e.preventDefault();
  }
});

hogeクラスを持つ要素がinactiveクラスを持っている場合、当該要素のデフォルト動作を無効化

stopPropagation()

stopPropagation()メソッドは、

イベントの親要素への伝播を無効化します。

このメソッドが記述された子要素をいくらクリックしたとしても、

その親要素にクリックイベントは到達しません。

以下のようなHTMLの構造下において、

hugaクラスを持つdivのイベントリスナーにstopPropagation()を設定すると、

子要素hugaをクリックしても、

その親要素であるhogeに登録されたイベントリスナーは発火しなくなります。

<div class="hoge">
  <div class="huga">
  </div>
</div>
document.querySelector(".hoge").addEventListener("click", e => {
  e.currentTarget.style.backgroundColor = "#ff0000";
});

document.querySelector(".huga").addEventListener("click", e => {
  e.stopPropagation();
  e.currentTarget.style.backgroundColor = "#7cfc00";
});

stopImmediatePropagation()

stopImmediatePropagation()メソッドは、いわばstopPropagation()の親戚で、

こちらは親要素への伝播に加え

同要素に設定された他のイベントリスナーへのイベント伝播も無効化します。

イベントリスナーは同要素に複数登録された場合、

ソースコード上で上にあるものから実行されていくため、

何かしらの理由でその流れを止めたい時に使用します。

ただ同イベントリスナー内の処理は当然のように処理されるので、

条件次第で実行の可否を決めたい処理がある場合には、

中断前後の処理を別イベントリスナーに分けるなどの工夫が必要です。

document.querySelector(".hoge").addEventListener("click", e => {
  e.stopImmediatePropagation();
  e.currentTarget.style.backgroundColor = "#ff0000";
});

document.querySelector(".hoge").addEventListener("click", e => {
  e.currentTarget.style.backgroundColor = "#7cfc00";
});

2番目のイベントリスナーは発火しないため、hogeの背景色は赤のままとなる。

最後に

以上のプロパティ・メソッドについてしっかり理解しておけば、

複雑なイベント処理の実装・エラーの解消がスムーズになり、

快適なイベントリスナー生活(?)が送れるようになるかと思います。

少々ややこしい部分もありますが、是非参考にしてみてください!

ところで…ロジカルスタジオではフロントエンドエンジニアを募集しております。

イベントリスナーと友達になりたい、または既に友達の方!コチラからご応募お待ちしております↓