By U Zensen.

欲しいものはプログラミングで自分で作る。

JavaScriptのイベントバブリングという仕様について本気出して考えてみた!!

2016 / 7 / 6 2016 / 10 / 30
JavaScript
イベントバブリング

はじめに!!

お疲れ様です!JavaScript大好き、ぴーすけです!

みなさんはJavaScriptのイベントについて、詳しく説明ができますか?

正直、私はいまいちわからずにコーディングしていたのですが、これじゃあいかん!と思い色々調べていたところ、「イベントバブリング」という単語に行き着きました。

イベントの伝播を理解するには必要な知識だと思うので、私のわかる範囲でまとめてみたいと思います。

スポンサードリンク

そもそもイベントってなんなんだよ!!

まずはこれが気になりました。イベントってなんなのよ。

以下の考え方はあくまで私の見解です。間違っていたら指摘をしていただけると嬉しいです。詳しい人がいたら教えてください!!

イベントとは

JavaScriptでよく「イベントを設定する」とか言うけど、なんかおかしいなーとおもっていたんですよね。だってイベント自体は自分で定義していないですよね?

イベント自体は元から定義されていて、そのイベントにJavaScriptを使って処理を紐付けさせてもらっているだけ。

イベント自体はブラウザがHTMLの読み込みやユーザの挙動に反応して起こるものであって、JavaScriptという言語自体の機能ではないと私は思っています。あくまでイベントはブラウザの実装部であり、そのイベントにJavaScriptを利用して処理を与えられるというだけ。

なので、イベントに処理が紐付いていようといまいと、画面の読み込みが終われば「loadイベント」が発生するし、何もないところでもユーザがクリックすれば「clickイベント」は発生している。

だから「イベントを設定する」というよりは「イベントに設定する(処理を)」というイメージ。

イベントハンドラとは

そしてそういったイベントに処理を与えることをイベントハンドラというのではないかと思っています。

window.onload = 処理;

 //とか

<div onclick="処理">

 //など

このようにwindowオブジェクトに用意された「onイベント名」という変数に処理を設定することを「イベントハンドラ」と呼びます。

イベントリスナとは

さらにイベントリスナという言葉もあります。イベントハンドラとの違いが分かりづらいですよね…。

JavaScript以外でも使われる言葉で、一般的には「イベントに対応するメソッド」のことを言うみたい。

JavaScriptでは以下のように「addEventLintener」や「on」を使ってイベントに処理を紐付けることをイベントリスナと呼ぶみたいです。

window.addEventListener('load', 処理);//JavaScript

 //とか

$(要素).on('click', 処理);//jQuery

 //など

難しいですねー。でもこれは本題ではありません。

本題!イベントバブリングって知っていますか!?

イベントバブリングとは「イベントが発生した要素から親要素に向けてイベントが伝播すること」をいいます。

バブリングとは「泡」という意味です。発生元からふわふわ上に上がることからこういった名前が付いているのかと思います。

こちらの図をご覧ください。

event

この図はイベントが発生した際の流れの図です。

例えば、上の図のように<td>をクリックして「clickイベント」が発生したとします。その場合、イベント発生した要素(<td>)を探すためにwindowからDOMツリーをたどって発生元の要素までイベントが伝わります。

この場合は「window→document→html→body…発生元」というように、親からイベント発生元を目指して順々に「clickイベント」が走ります。このことを「キャプチャーフェーズ」と言います。

そして実際にイベントが発生した要素にイベントが伝わったことを「ターゲットフェーズ」といいます。

さらに、ターゲットフェーズが終わったら、そのイベント発生元から親に向けてイベントが走ります。「発生元…body→html→document→window」という感じです。このことを「バブリングフェーズ」と言います。

「キャプチャーフェーズ」「ターゲットフェーズ」「バブリングフェーズ」それぞれのフェーズできちんとイベントが発生します。

例えば、<td>が発生元の「clickイベント」が発生したとします。その際に、windowやbodyの「clickイベント」に処理を紐付けていた場合はそれらの処理もきちんと発火するのです。

イベントは親から子へ、子から親へ

この下にイベントの伝播のサンプルを用意しました。左側の四角を色々とクリックしてみてください。もちろんタッチにも対応しています!

ちなみに、

赤がキャプチャーフェーズ
青がターゲットフェーズ
緑がバブリングフェーズ

です。

  • キャプチャーフェーズ
  • ターゲットフェーズ
  • バブリングフェーズ

イメージはつかめましたでしょうか?

え、ってことはイベント発生元の親要素に設定したイベントは2回発生するの!?

って思いませんか?でも実際はそんなことはないですよね。例えば、以下のようにwindowにクリックに紐づくalert()を設定したとします。

//windowがクリックされた時にアラートを出す
window.addEventListener('click', function(e) {
  alert("windowにクリックイベントが発生しました");
});

windowの子要素にイベントが発生した場合には、「キャプチャーフェーズ」の時と「バブリングフェーズ」の時、合わせて2回イベントが発火してしまう気がしますが、実際はそうではありません。

実はこの「addEventListener」の3つ目の引数が鍵を握っています。

要素.addEventListener(イベント名, 処理, 真偽値);
イベント名
clickやtouchstartなどのイベント名を入れます。
処理
イベント発生時に動く処理を設定します。
真偽値
初期値はfalseです。値を入れない場合はfalseになります。falseの場合は「バブリングフェーズ」に処理が発火します。trueの場合は、「キャプチャーフェーズ」で処理が発火します。

ということみたいです。なので、処理の発生タイミングはこちらで選ぶことができます。

例えば以下のような感じ。

//以下の場合は「キャプチャーフェーズ」でalertが表示されます。
//つまり発生元の処理が走る前にwindowの処理が走るということ。
window.addEventListener('click', function(e) {
  alert("windowにクリックイベントが発生しました");
}, true);
//以下の場合は「バブリングフェーズ」でalertが表示されます。
//つまり発生元の処理が走った後にwindowの処理が走るということ。
window.addEventListener('click', function(e) {
  alert("windowにクリックイベントが発生しました");
});

//以下の記述でも同じ

window.addEventListener('click', function(e) {
  alert("windowにクリックイベントが発生しました");
}, false);

普段みなさんがイベントに処理を紐付ける際には、なにも意識せずに「バブリングフェーズ」にイベントが発生するように処理を紐付けているのではないかと思います。

それでも特に問題ないことが多いかと思いますが、知っていてやっているのと知らないでやってしまっているのは全然違うので、頭の片隅に置いておいてもいいかもしれません。

じゃあ、同じ要素にキャプチャーフェーズとバブリングフェーズの両方に処理は付けられるの!?

安心してください。付けられます。

//キャプチャーフェーズで発火させる
window.addEventListener('click', function(e) {
  alert("windowにクリックイベントが発生しました(キャプチャー)");
}, true);

//クリックするつもりのやつ
document.getElementById('target').addEventListener('click', function(e) {
  alert("クリックされた!!");
});

//バブリングフェーズで発火させる
window.addEventListener('click', function(e) {
  alert("windowにクリックイベントが発生しました(バブリング)");
});

idがtargetな要素をクリックした結果

windowにクリックイベントが発生しました(キャプチャー) クリックされた!! windowにクリックイベントが発生しました(バブリング)

ちなみにjQueryでの書き方は!?

調べてみたんですが、jQueryではフェーズを選べないみたい…?

下記サイトによると、ほとんどのイベントはバブリングフェーズで固定されていて、focusとblurイベントだけはキャプチャーフェーズ固定されているみたいですね。

まとめ!?

長くなってしまいましたが、以上が私が調べて考えた認識です。

JavaScriptは普段意識しない裏側の仕様がたくさんあるので、理解していないとなんでそうなるの?ということがたくさんあると思います。巻き上げとかとか。

私の認識を書きなぐったため、もしかしたら間違っているところや曖昧なところもあるかと思います。なにか気がついたことがありましたらご指摘いただければ幸いです。

Pocket

コメント

コメントを残す