blog

DOMイベントモデルとイベントプロキシ

ブラウザのイベントモデルは、関数をリッスンすることでイベントに反応します。イベントが発生すると、ブラウザはイベントをリッスンし、対応するリスナー関数を実行します。これがイベント駆動型プログラミングモデ...

Oct 31, 2020 · 6 min. read
シェア

ブラウザのイベントモデルは、関数をリスニングすることでイベントに反応するものです。イベントが発生し、ブラウザがそれをリッスンすると、対応するリスナー関数を実行します。これがイベント駆動型プログラミングモデルの主なプログラミング手法です。

こんなCSSコードがあったとします。

<div class="grandpa">
 <div class="dad">
 <div class="kids">kid1</div>
 <div class="kids">kid2</div>
 <div class="kids">kid3</div>
 </div>
</div>

リスナー関数

このdivをクリックしたときに、この要素ノードを操作するにはどうすればよいでしょうか?Javascriptには、イベントにリスナー関数をバインドする3つの方法があります。

HTML のon属性は

HTML言語では、要素の属性に直接、特定のイベントをリッスンするためのコードを定義することができます。

<body onload="doSomething()">
<div onclick="console.log('イベントをトリガーする')">

上記のコードでは、bodyノードのloadイベントとdivノードのclickイベントのリスニングコードを指定しています。イベントが発生すると、このコードが実行されます。要素のイベントリスナー属性は、on + イベント名で、例えば onload は on + load で、load イベントのリスナーコードを表します。これらの属性の値は実行されるコードであり、関数ではないことに注意してください。

要素ノードのイベント属性

要素ノードオブジェクトのevent属性も同様にリスナー関数を指定できます。

window.onload = doSomething;
div.onclick = function (event) {
 console.log('イベントをトリガーする');
};

このメソッドで指定されたリスナー関数も、バブリングフェーズ中にのみトリガーされます。

このメソッドとHTMLのon-attributeの違いは、後者のようにリスニングコード全体を指定しなければならないのとは異なり、その値が関数名であるということです。

EventTarget.addEventListener()

すべての DOM ノードインスタンスには addEventListener メソッドがあり、そのノードのイベントのリスナー関数を定義します。

window.addEventListener('load', doSomething, false);

まとめ

上記の3つの方法は、最初の "HTMLのon属性 "は、HTMLとJavaScriptコードの分離の原則に違反している、2つは一緒に書かれている、労働コードの分割に資するものではなく、したがって、使用することをお勧めしません。

つまり、onclick属性が2回定義された場合、後者の定義が前者の定義より優先されます。したがって、これも推奨されません。

ventTarget.addEventListener3番目のEは、リスナー関数を指定する際に推奨される方法です。次のような利点があります:

  • 同じイベントに複数のリスナー関数を追加できます。
  • リスナー関数がどの段階でトリガーされるかを指定する機能。
  • このインターフェースはDOMノード以外のオブジェクトでも利用可能で、JavaScript全体で統一されたリスナー関数インターフェースに相当します。

さて、CSSコードに戻りましょう。私たちが子供をクリックしたとしましょう。おじいちゃんをクリックしましたか?

イベントの伝播

イベントが発生すると、子要素と親要素の間で伝播します。この伝播は3つの段階に分けられます。

  • 第一段階:ウィンドウオブジェクトからターゲットノードまで。
  • フェーズ2:ターゲットノードでトリガーされ、「ターゲットフェーズ」と呼ばれます。
  • 第3段階:ターゲット・ノードからウィンドウ・オブジェクトに戻る伝導。

この3段階の伝搬モデルにより、同じイベントを複数のノードでトリガーすることができます。

<div class="grandpa">
 <div class="dad">
 <div class="kids">kid1</div>
 <div class="kids">kid2</div>
 <div class="kids">kid3</div>
 </div>
</div>

上記のコードでは、ノードの中にノードがあります。

3つのキッズノードすべてにクリックイベントのリッスン関数を設定すると、合計4つのリッスン関数を設定することになります。すると、``click''の場合、クリックイベントは7回トリガーされることになります。

var phases = {
 1: 'capture',
 2: 'target',
 3: 'bubble'
};
var kids = document.querySelector('.kids');
var dad = document.querySelector('.dad');
kids.addEventListener('click', callback, true);
dad.addEventListener('click', callback, true);
kids.addEventListener('click', callback, false);
dad.addEventListener('click', callback, false);
function callback(event) {
 var tag = event.currentTarget.tagName;
 var phase = phases[event.eventPhase];
 console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// クリック後の結果
// Tag: 'DIV'. EventPhase: 'capture' runner-3.25.5.min.js:1:9180
// Tag: 'DIV'. EventPhase: 'target' 2 runner-3.25.5.min.js:1:9180
// Tag: 'DIV'. EventPhase: 'bubble' runner-3.25.5.min.js:1:9180
// Tag: 'DIV'. EventPhase: 'capture' runner-3.25.5.min.js:1:9180
// Tag: 'DIV'. EventPhase: 'bubble' runner-3.25.5.min.js:1:9180
// Tag: 'DIV'. EventPhase: 'capture' runner-3.25.5.min.js:1:9180
// Tag: 'DIV'. EventPhase: 'bubble'

ノードのキャプチャフェーズとバブリングフェーズでそれぞれ3回、ノードのターゲットフェーズで1回です。

  1. キャプチャーフェーズ: ``クリックイベント(``click event)はイベントがto.からto.に伝搬したときにトリガーされます;
  2. 対象フェーズ:クリックイベントがトリガーされ、イベントが到着;
  3. バブリングフェーズ:パスバックからイベントが返されると、``クリック''イベントが再びトリガーされます。

このノードにはリスナー関数が2つあるので、両方とも発火する。`click`イベントは一度だけトリガーされる。だから目標段階では3つのアウトプットがあります。

ブラウザは常にクリックイベントのターゲットノードはクリック位置が最も深くネストされたものであると仮定していることに注意してください。そのため、``node''のキャプチャフェーズとバブリングフェーズの両方がターゲットフェーズとして表示されます。

つまり、上の例でのイベント伝播の順序は、キャプチャフェーズではwindow、document、html、body、div、pの順、バブリングフェーズではp、div、body、html、documentの順となります、window

イベントのプロキシ

イベントはバブリングフェーズ中に親ノードへ上向きに伝搬するため、親ノード上で子ノードのリスニング関数を定義し、親ノードのリスニング関数に複数の子要素のイベントの処理を統一させることができます。このアプローチはイベントのプロキシと呼ばれます。

var ul = document.querySelector('ul');
ul.addEventListener('click', function (event) {
 if (event.target.tagName.toLowerCase() === 'li') {
 // some code
 }
});

上のコードでは、クリックイベントのリスナー関数をノードで定義していますが、実際には子ノードのクリックイベントを処理します。この利点は、一つのリスナー関数を定義することで、``ノード``ごとにリスナー関数を定義することなく、複数の子ノードのイベントを処理できることです。また、後から子ノードを追加しても、リスナー関数は有効です。

特定のノードまでイベントの伝播を止めたい場合は、event オブジェクトの stopPropagation メソッドを使用します。

// イベントがp要素に伝搬したら、もう下には伝搬しない
p.addEventListener('click', function (event) {
 event.stopPropagation();
}, true);
// イベントがp要素までバブルアップしたら、上へのバブルアップを止める。
p.addEventListener('click', function (event) {
 event.stopPropagation();
}, false);

上記のコードでは、stopPropagationメソッドは、それぞれキャプチャフェーズとバブリングフェーズ中のイベントの伝播を停止します。

しかし、stopPropagation メソッドはイベントの伝播を止めるだけで、そのイベントが ``ノードの他のクリックイベントのリスナー関数をトリガーするのを防ぐことはできません。つまり、クリックイベントの完全なキャンセルではありません。

p.addEventListener('click', function (event) { event.stopPropagation(); console.log(1); }); p.addEventListener('click', function(event) { // console.log(2); });

上記のコードでは、p要素は2つのクリック・イベント・リスナーにバインドされています。stopPropagationメソッドはこのイベントの伝播を止めることができるだけで、キャンセルすることはできないので、2番目のリスナー関数がトリガーされます。出力は1、2の順になります。

stopImmediatePropagationイベントを完全にキャンセルし、後続のすべてのクリックリスナー関数のトリガーを停止したい場合は、このメソッドを使用できます。

p.addEventListener('click', function (event) { event.stopImmediatePropagation(); console.log(1); }); p.addEventListener('click', function(event) { // トリガーされない console.log(2); });

stopImmediatePropagation上記のコードでは、メソッドはイベントを完全にキャンセルするので、その後にバインドされたクリックリスナー関数はすべてトリガーされなくなります。したがって、出力されるのは2ではなく1だけです。

Read next

HTTPを理解するには手作業が一番だ (3)

本日の課題は「[簡単] cpptomlを導入してファイルから設定を読み込む、spdlogを導入してデバッグに役立つログを打ってみる」です。 ご存知のように、コンパイルされたクラスのプログラムは、一度コンパイルすれば、何度でも実行することができ、いくつかのテストに合格すれば、一年中動作することができます。しかし、いくつかのケースは、いくつかのメソッドを介してプログラムの動作を変更しようとします、例えば:ポートをリッスンするだけでなく、プログラムの設定項目は、実際には、あります...

Oct 31, 2020 · 5 min read