uhyohyo.net

JavaScript初級者から中級者になろう

三章第四回 イベントキャプチャリング

イベントキャプチャリング

いきなりですが、次のサンプルを見てみましょう。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <div id="aaa">
      <p onclick="console.log('p');">test</p>
    </div>

    <script type="text/javascript">
      var div = document.getElementById('aaa');

      div.addEventListener('click',function(){
          console.log('div');
      },true);

    </script>
  </body>
</html>

p要素にonclick属性がついています。

また、script要素内では、変数divにgetElementByIdでdiv要素を取得して代入し、そのaddEventListenerを呼び出しています。今までと違うのは3つ目の引数です。3つめの引数は、三章第二回で「とりあえずfalseにしておきましょう」といったものですが、trueになっています。これが今回の鍵です。

さて、このサンプルを実行してみます。onclick属性があり、またaddEventListenerでも「click」を登録しているので、とりあえずP要素をクリックしてみます。木構造は

div
|
└―― p
          

というようになっていますね。ここで、前回解説したイベントバブリングの仕様に従うと、p要素をクリックすると、まずp要素でイベントが発生し、onclick属性で設定されたconsole.log('p');が実行されて「p」のログが表示されます。

その後、

div

└――p

このように親要素であるdivに伝わり、divでイベントが発生します。divにはaddEventListenerで

function(){
    console.log('div');
}

が登録してあるので、それが実行されて「div」が表示されます。

まとめると、「p」がまず表示されて、その後「div」が表示されると予想できます。

しかし、実際は違います。クリックしてみると、先に表示されるのはなんと「div」で、その後「p」が表示されます。ここに、イベントキャプチャリングが関わってきます。

イベントキャプチャリングとは、イベントバブリングの前に、親要素から子要素に向けてイベントが伝わるというものです。

つまり、今回の場合、イベントバブリングで「p → div」という子から親の流れのに、「div → p」という親から子の流れがあったということです。

そして、実はaddEventListenerの3つめの引数は、そのイベントリスナ(関数)をイベントキャプチャリングで処理するならtrueイベントバブリングで処理するならfalseということになっています。ちなみに、イベント属性で登録されたイベントは、イベントバブリングで処理されることになっています。

より具体的に見ていくと、まず、

div
|
└―― p     ←ここ

でイベントが発生したとします。すると、イベントキャプチャリングの仕様にしたがって、まず

div     ←ここ
|
└―― p

から処理されます(もちろん、本当は、もっと上のhtml要素やbody要素から処理されますが、今回は簡略化のためにdivが一番上ということにしています)。ここで見られるのは、イベントキャプチャリングで処理するように登録された、つまりaddEventListenerの第三引数がtrueで登録されたものです。

今回、'div'をログする関数がこれにあたるので、ここでまず先に「div」が表示されたというわけです。

そのあと、親から子へ、

div

└―― p     ←ここ

このように伝わり、p要素が処理されます。しかし、p要素のイベント属性はイベントバブリングのほうで処理されるので、ここでp要素が処理するものはありません。よって、何も起こらず次に進みます。

ここからは前回解説したイベントバブリングです。まず

div
|
└―― p     ←ここ

が処理されます。上と同じp要素ですが、今度はイベントバブリングだから、イベント属性の処理が処理され、「p」が表示されます。その後、

div     ←ここ
  |
└―― p

このように伝わり、div要素が処理されます。しかし、div要素にはイベントバブリングで処理されるものはないので、何も起こらず、終了します。これが一連の流れです。

さて、ここで、上のサンプルを

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <div id="aaa" onclick="console.log('div2');">
      <p onclick="console.log('p');">test</p>
    </div>

    <script type="text/javascript">
      var div = document.getElementById('aaa');

      div.addEventListener('click',function(){
          console.log('div');
      },true);
    </script>
  </body>
</html>

のようにしてみたら、どうなるか分かりますか? そう、div要素についたイベント属性はバブリングフェーズで処理されるから、p要素の「p」よりも後、つまり、「div」「p」「div2」の順番でログが表示されます。

フェーズ

さて、いまイベント処理の一連の処理をみてきましたが、実はこの流れは3つの段階に分けることができます。そのひとつひとつはフェーズと呼ばれます。

まず、イベントが発生して、イベントキャプチャリングの仕様にそって親から子へイベントを見ていく処理があります。この処理は、キャプチャリングフェーズといいます。

その後、発生源の要素に到達し、その要素の処理を行います。これは今までイベントバブリングに含めていましたが、厳密にはここだけを抜き出してターゲットフェーズといいます。

そして、イベントバブリングの仕様に従って、発生源から親へイベントを見ていく段階がバブリングフェーズです。

この3段階になっています。この一連の流れを、全部あわせてイベントフローといいます。

今回はイベントキャプチャリングを紹介しましたが、使う機会はそれほど多くありません。ほとんどの場合はイベントバブリングだけで事足ります。イベントキャプチャリングは、イベントバブリングで処理される本処理の前にちょっと準備処理のようなものが必要だという場合に使われることがあります。