CaptureとBubbleイベントフェーズ


Web開発をしたことがあるとaddEventListenerは、3つのパラメータを受け入れていることを知っているはずです.最初の2つはよく知っていて、それぞれイベントタイプとイベントプロセッサを表していますが、最後のパラメータはuseCaptureで、少し理解できません.booleanパラメータで、trueの場合はCaptureフェーズでイベントをトリガし、falseの場合はBubbleフェーズでイベントをトリガします.これまでこのパラメータにあまり注目していなかったが、IEがAttachEventを使用してイベントプロセッサを登録していることが主な原因だったが、useCaptureはサポートされておらず、一律にバブル段階でイベントをトリガーすることを示しているため、IEという長兄を互換させるためにaddEventListenerのuseCaptureは通常falseに設定されている.jQueryのようなより高度なクラスライブラリを使用すると、これらを作成できます.しかし、私は最近Firefoxの下のプラグインの開発をしています.FirefoxがuseCaptureパラメータをサポートしていることを知っています.falseを使っているところもあれば、trueを使っているところもありますが、私はそれがなぜ使われているのか分かりません.これはいつも不快です.そのため、時間をかけてそれを明らかにしました.このブログが話しています.
なぜCaptureとBubbleの2つの段階があるのかを明らかにするには、実際にイベントをトリガーする要素(target)を加えて3つの段階があり、まずイベントの伝播を明らかにしなければならない.イベントが伝播する必要があるのは、イベントの実際のトリガがイベントプロセッサを登録していない可能性が高く、その親要素に同じイベントのプロセッサが登録されている場合にのみ、イベントが親要素に伝播するためです.以下のHTMLドキュメントを見てみましょう.

<html>
<body>
	<p id="the_p">This is a <a id="the_a" href="#"><span id="the_span">SAMPLE</span></a> text.</p>
</body>
<script>
	function clickHandler(event) {
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
	}
	document.getElementById('the_a').addEventListener('click', clickHandler, false);
	document.getElementById('the_p').addEventListener('click', clickHandler, false);
</script>
</html>

上記の例では、AとP要素にそれぞれ同じイベントプロセッサを登録していますが、SPAN要素に登録していません.「SAMPLE」をクリックしたときに実際にクリックしたSPAN要素は、SPANにイベントプロセッサがないため無視されているのではないでしょうか.明らかにそうではありません.私たちはAとPにプロセッサを登録しているので、AとPにとって、SPANをクリックするのはAとPをクリックするのと同じです.もちろんBODYとDOCUMENTもクリックしています.だから、AとP上のイベントプロセッサをトリガーします.つまり、イベントはSPANからAに伝播し、BODYとDOCUMENTに伝播する必要があります.各伝播された要素は、このイベントを処理する機会があります(ここで説明する伝播順序は準備されていません.以下で説明します).
各イベントプロセッサはイベントタイプに応じてかなりの属性を持つeventパラメータを受け入れているが、一般的にはいくつかあり、上記の例では2つの重要な属性を使用している.targetはイベントの実際のトリガを表し、この例ではSPANであり、currentTargetはイベントを処理している要素を表し、Aがこのイベントを処理している場合、ではCurrentTargetはA要素であり、P要素がイベントを処理している場合event.CurrentTargetはP要素です.つまり同じ事件に対してtargetは常にイベントのトリガでありevent.誰がこの事件を処理しているかによって決まるCurrentTargetは常にthisに等しい(JavaScriptでthisを使用するには、JavaScriptがcallまたはapplyでthisを変更できるため、特に注意する必要があります).
もう一つの問題は、AとPのイベントプロセッサがトリガする以上、それは先にトリガされますか?これは、キャプチャフェーズ、イベントターゲットフェーズ、バブルフェーズの3つのフェーズに関連する.それらの順序はCaptureフェーズが最初で,Targetが中央,Bubbleフェーズが最後である.文字の説明があまりにも似ていて、上記の例では、SPAN要素をクリックすると、clickイベントの伝播は3つの段階に分けられます.

	Capture  :DOCUMENT -> BODY -> P -> A
	Target  :SPAN
	Bubble  :A -> P -> BODY -> DOCUMENT

CatpureフェーズとBubbleフェーズで経験した要素の順序が正反対であることがわかります.各イベントについてtarget要素を除いて、各要素は2回伝播されますが、そのイベントプロセッサは1回しかトリガできません.いったいどのトリガがuseCaptureパラメータに依存します.useCaptureがtrueであれば、最初に要素に伝播したときにトリガすることを示します(つまりCaptureフェーズ)、そうでなければ2回目に要素に伝播してからトリガすることを示します(つまりBubbleフェーズ).target要素の場合、一度しか伝播しないため、useCaptureを指定することは意味がありません(ただし、パラメータが必要であるため、指定する必要があります).これは常に伝播の中間点でトリガーされます(つまりTargetフェーズ).上記の例では、AおよびPがaddEventListenerを呼び出すときにuseCaptureはfalseであるため、いずれもBubbleフェーズでのみトリガーされる.これにより、SPANにはイベントプロセッサが登録されていないため、Targetフェーズでもイベントプロセッサはトリガーされず、Bubbleフェーズでは、イベントプロセッサはAとPでトリガーされるが、イベントがAに伝播してからPに伝播するため、AのイベントプロセッサはPより先にイベントプロセッサがトリガーされる.これは、実行結果から、まずポップアップされたアラートボックスがcurrent targetがAであることを示し、その後、current targetがPであるアラートボックスがポップアップされることがわかります.
登録イベントのプロセスを変更すると、次のようにCaptureフェーズでトリガーされます.

	document.getElementById('the_a').addEventListener('click', clickHandler, true);
	document.getElementById('the_p').addEventListener('click', clickHandler, true);

このような結果はどうなるのでしょうか.イベント伝播プロセスを分析すると,両イベントプロセッサともCaptureフェーズでトリガされ,CaptureフェーズではイベントがPに伝播してからAに伝播するため,PのイベントプロセッサがAより先にイベントプロセッサがトリガされるとすぐに結論が得られる.次に変えたらどうなるの?

	document.getElementById('the_a').addEventListener('click', clickHandler, false);
	document.getElementById('the_p').addEventListener('click', clickHandler, true);

すなわち,AのイベントプロセッサはBubbleフェーズでトリガし,PはCaptureフェーズでトリガし,イベント伝播プロセスを解析するが,Captureフェーズは常にBubbleフェーズより先になるため,Pのイベントプロセッサが先にトリガする.
イベント伝播が通過する要素がチェーンを形成していることがわかります.上記の例では、このチェーンはDOCUMENT->BODY->P->A->SPAN->A->P->BODY->DOCUMENTです.では、この伝播チェーンはどのように確定されていますか?実はとても简単で、読者もきっと予想することができて、事件が触発する时、まずtarget要素(ここではSPAN)を确定して、それからその父の要素(A)を确定して、更に父の要素(P)を探して、このようにずっと続けて、ドキュメントのルートの要素(DOCUMENT)を探して、これはBubbleの段阶の伝播の过程に相当して、事件の全体の伝播の过程を得て、このチェーンを逆にtarget要素を除去し,元のチェーンの先頭を追加するだけでイベントの伝播経路全体が得られる.パス全体は、イベントがトリガーされたときに決定され、イベント伝播プロセスでパスの一部の要素が削除されても、その上に登録されているイベントプロセッサがトリガーされます.例を見てみましょう.

	function pHandler(event) {
		// remove A
		document.getElementById("the_p").removeChild(document.getElementById("the_a"));
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
	}
	function aHandler(event) {
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
	}
	document.getElementById('the_p').addEventListener('click', pHandler, true);
	document.getElementById('the_a').addEventListener('click', aHandler, false);

PイベントプロセッサはCaptureフェーズでトリガされ、AはBubbleフェーズでトリガされるため、Pは先にトリガされ、そのイベントプロセッサではA要素が先に削除され、イベント情報が表示されます.Aが削除されたにもかかわらず、登録されたイベントプロセッサは、その削除が伝播中に削除され、イベントの伝播経路がイベントがトリガーされたときに決定されるためトリガーされます.
デフォルトではイベント伝播は完全な3段階を歩きますが、eventのstopPropagation()はイベントの伝播を中止できます.

	function clickHandler(event) {
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
		event.stopPropagation();
	}

	document.getElementById('the_p').addEventListener('click', clickHandler, true);
	document.getElementById('the_a').addEventListener('click', clickHandler, false);

上記の例では、まずCaptureフェーズでPのイベントプロセッサがトリガーされ、stopPropagation()メソッドが呼び出されてイベントの継続的な伝播を阻止するため、A要素はイベントを受信できません.IEのeventのcancelBubbleプロパティは、trueの場合にイベントがBubble上に上がるのを阻止します.jQueryのevent.stopPropagation()メソッドIEブラウザはeventのcancelBubbleプロパティを設定することによってイベントの伝播を阻止することを実現する.しかし、両者は意味的に異なり、stopPropagation()は任意のフェーズのイベント伝播を阻止し、cancelBubbleはBubbleフェーズのイベント伝播のみを阻止する.しかしIEの制約によりjQueryのような上位クラスライブラリはBubble方式のイベント伝播のみを使用し,stopPropagation()はcancelBubble(Captureがないため)であり,両者の機能は同じである.
これだけ言うと、読者はCaptureとBubbleの二つの事件の伝播段階を知っているだろう.いつCaptureを使うべきか、いつBubbleを使うべきか、読者自身に裁決してもらう.大まかな原則は、イベントをできるだけ早く実行したい場合はCaptureを使用し、そうでなければBubbleを使用します.しかし、一つの前提を忘れないでください.IEはBubbleの伝播方式だけをサポートしています.もしあなたのアプリケーションがIEをサポートする必要があるならば、それはずっと簡単です.それは永遠にuseCaptureをfalseに設定することです.