JavaScriptのイベントキャプチャリング、イベントバブリングについて、よく分からなかったので、図を交えて理解してみました。
イベントバブリングについて
突然ですが、以下のコードが表示されたブラウザで "テスト"の文字をクリックしたらどうなるでしょうか?
<!doctype html>
<html>
<head>
<title>test</title>
</head>
<body>
<div id="div">
<p>テスト</p>
</div>
<script type="text/javascript">
const div = document.getElementById('div');
const sayHello = () => alert('hello');
div.addEventListener('click', sayHello);
</script>
</body>
</html>
アラートで"hello"と表示されますよね。
では、なぜhelloと表示されるのでしょうか?
div要素にクリックイベントが登録されており、そのdiv要素がクリックされたからでしょうか?
厳密には、div要素がクリックされたからではありません。
p要素の子要素のテキストノードがクリックされたからです。
上記のhtmlの構造を下記の積み木の様に親要素の上に子要素が乗っかっているイメージをすると分かりやすいかもしれません。
まず、最初にテキストノードがマウスによってクリックされました。テキストノードがクリックされると親の要素(この場合pですね。p要素がクリックされたとイメージして差し支えありません。)でクリックイベントを検知します。
さて、p要素でクリックイベントを検知しました。が、pは特にaddEventListner等でクリックイベントに対して何も登録されていないので何も起こりません。
次にp要素は親要素のdiv要素に対し、クリックイベントが発生したことを伝えます。
ここで、div要素はクリックイベントに対しsayHello関数が登録されていますね。
よって、sayHello関数が実行されてアラートで"hello"が表示されます。
この緑色の矢印部分、つまり親に対しイベントの発生を伝える部分を イベントバブリング と言います。
下記の様にコードを修正し、「テスト」をクリックした場合に、「hello」「hello2」の順番にアラートが表示されて、順に親にクリックイベントが伝わっているのがわかります。
<!doctype html>
<html>
<head>
<title>test</title>
</head>
<body>
<div id="div2">
<div id="div">
<p>テスト</p>
</div>
</div>
<script type="text/javascript">
const div = document.getElementById('div');
const sayHello = () => alert('hello');
div.addEventListener('click', sayHello);
const div2 = document.getElementById("div2");
const sayHello2 = () => alert('hello2');
div2.addEventListener('click', sayHello2);
</script>
</body>
</html>
実際には、祖先(最も上の親のWindowオブジェクト)まで、伝播していきます。
イベントキャプチャリングについて
実際に上記の例のようにクリックイベントが発生した場合、イベントバブリングの伝播の前に、イベントキャプチャリングの伝播がなされます。
イベントキャプチャリングはイベントバブリングとは逆で、イベントが発生した要素の祖先(最も上の親のWindowオブジェクト)から順に子要素へとイベントが伝播していきます。
イベントキャプチャの伝播時に実行する関数を登録すには、addEventListenerメソッドの第三引数に true を渡します。(第三引数にfalseを渡した場合はイベントバブリング。また省略した場合はfalseとなります。)
<!doctype html>
<html>
<head>
<title>test</title>
</head>
<body>
<div id="div2">
<div id="div">
<p>テスト</p>
</div>
</div>
<script type="text/javascript">
const div = document.getElementById('div');
const sayHello = () => alert('hello');
div.addEventListener('click', sayHello, true);
const div2 = document.getElementById("div2");
const sayHello2 = () => alert('hello2');
div2.addEventListener('click', sayHello2, true);
</script>
</body>
</html>
この場合は、sayHello,sayHello2関数ともにイベントキャプチャの伝播時に発火するため「hello2」が出力後「hello」が出力されます。
下記の様に、それぞれの要素にイベントキャプチャリング伝播時に発火するメソッドと、イベントバブリング伝播時に発火するメソッドが登録されています。
<!doctype html>
<html>
<head>
<title>test</title>
</head>
<body>
<div id="div2">
<div id="div">
<p id="p">テスト</p>
</div>
</div>
<script type="text/javascript">
const p = document.getElementById("p");
const pCap = () => console.log('pキャプチャ');
const pBub = () => console.log('pバブル');
p.addEventListener('click', pCap, true);
p.addEventListener('click', pBub);
const div = document.getElementById('div');
const divCap = () => console.log('divキャプチャ');
const divBub = () => console.log('divバブル');
div.addEventListener('click', divCap, true);
div.addEventListener('click', divBub);
const div2 = document.getElementById("div2");
const div2Cap = () => console.log('div2キャプチャ');
const div2Bub = () => console.log('div2バブル');
div2.addEventListener('click', div2Cap, true);
div2.addEventListener('click', div2Bub);
</script>
</body>
</html>
この場合は下記の図の様な順番でメソッドが実行されていきます。
まずは、イベントキャプリャリングの伝播が始まります。
div2Cap、divCap、pCapの順に実行されます。
伝播がイベント発生元(p要素)まで達するとイベントバブリングによる伝播が始まります。
pBub、divBub、div2Bubの順に実行されます。
よって最終的にログは下記の順番となります。
div2キャプチャ
divキャプチャ
pキャプチャ
pバブル
divバブル
div2バブル
続く...
【JavaScript】イベントキャプチャリング、イベントバブリングについて図解で理解する 2 〜 イベント移譲 〜。