JavaScript イベントについて話すとき、インタビューでも日常の JavaScript 開発でも、イベント バブリング、イベント キャプチャ、イベント委任の 3 つのトピックを避けるのは困難です。JavaScript のこれらのイベントに慣れていない学生のために、見てみましょう。この記事で!
イベントフロー
JavaScriptとHTML間の対話はイベントを通じて実現されます。イベントは、ドキュメントまたはブラウザ ウィンドウ内で発生する対話の特定の瞬間です。リスナーを使用してイベントをサブスクライブし、イベントの発生時に適切なコードが実行されるようにすることができます。
イベントストリームの起源: ブラウザが第 4 世代に開発されたとき、ブラウザ開発チームは問題に遭遇しました: ページのどの部分に特定のイベントが発生するか?この質問が何を尋ねているのかを理解するには、紙の上に描かれた一連の同心円を想像してください。円の中心に指を置くと、指は 1 つの円ではなく、紙上のすべての円を指します。つまり、ページ上のボタンをクリックすると、そのボタンのコンテナ要素をクリックするか、ページ全体をクリックすることもあります。ただし、IE はバブリング ストリームを提案するのに対し、Netscape はキャプチャ ストリームを提案します。
例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>事件流</title> <style type="text/css"> #content { width: 150px; height: 150px; background-color: red; } #btn { width: 80px; height: 80px; background-color: green; } </style> </head> <body> <p id="content">content <p id="btn">button</p> </p> <script type="text/javascript"> var content = document.getElementById("content"); var btn = document.getElementById('btn'); btn.onclick = function () { alert("btn"); }; content.onclick = function () { alert("content"); }; document.onclick = function () { alert("document"); } </script> </body> </html>
コンテナ #btn をクリックすると、ポップアップの順序は btn-content-document となり、コンテナ #content をクリックすると、ポップアップは content-document になります。 、ポップアップはドキュメントです。
ここから、JavaScriptのイベントフローの仕組みが分かります
前述したように、IEはバブリングフローを提案し、Netscapeはキャプチャフローを提案しました。その後、W3C組織の統一のもと、JSはバブリングフローとキャプチャストリームをサポートしました。 , ただし、現在の低バージョンの IE ブラウザ はまだバブリング ストリームのみをサポートしています (IE6、IE7、および IE8 はすべてバブリング ストリームのみをサポートしています)。そのため、より多くのブラウザーと互換性を持たせるために、バブリング フローを使用することをお勧めします。
JSイベントフローの模式図は以下の通りです
これで分かります
1. 完全なJSイベントフローはウィンドウから始まり、最後にウィンドウに戻る処理です
2.イベントフローは3段階に分かれており、(1~5)キャプチャ処理、(5~6)ターゲット処理、(6~10)バブリング処理
例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> #wrapp, #innerP, #textSpan { margin: 5px; padding: 5px; box-sizing: border-box; cursor: default; } #wrapp { width: 300px; height: 300px; border: indianred 3px solid; } #innerP { width: 200px; height: 200px; border: hotpink 3px solid; } #textSpan { display: block; width: 100px; height: 100px; border: orange 3px solid; } </style> </head> <body> <p id="wrapp">wrapp <p id="innerP">innerP <span id="textSpan">textSpan</span> </p> </p> <script> var wrapp = document.getElementById("wrapp"); var innerP = document.getElementById("innerP"); var textSpan = document.getElementById("textSpan"); // 捕获阶段绑定事件 window.addEventListener("click", function (e) { console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.addEventListener("click", function (e) { console.log("document 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.documentElement.addEventListener("click", function (e) { console.log("documentElement 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.body.addEventListener("click", function (e) { console.log("body 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); wrapp.addEventListener("click", function (e) { console.log("wrapp 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); innerP.addEventListener("click", function (e) { console.log("innerP 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); textSpan.addEventListener("click", function (e) { console.log("textSpan 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); // 冒泡阶段绑定的事件 window.addEventListener("click", function (e) { console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.addEventListener("click", function (e) { console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.documentElement.addEventListener("click", function (e) { console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.body.addEventListener("click", function (e) { console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); wrapp.addEventListener("click", function (e) { console.log("wrapp 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); innerP.addEventListener("click", function (e) { console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); textSpan.addEventListener("click", function (e) { console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false);</script> </body> </html>
この時、textSpan要素をクリックすると、コンソールは次のような内容を出力します:
上記で描かれたイベント伝播プロセスから、マウスがクリックされると、最初にイベントのキャプチャが発生することがわかります
1 · キャプチャフェーズ: まず、windowがキャプチャされます。イベントに到達した後、document、documentElement、bodyがキャプチャされ、その後イベントはのレイヤーごとにキャプチャされますbodyDOM要素があります。wrapp、innerP。
2・ターゲットフェーズ: 実際にクリックされた要素textSpanのイベントが2回発生します。これは、上記のJavaScriptコードでは、textSapnの両方がキャプチャフェーズでイベントをバインドしており、バブルフェーズのリスクを伴うためです。はイベントにバインドされているため、2 回発生します。ただし、ここで 1 つ注意する必要があります。ターゲット フェーズでは、キャプチャ フェーズでバインドされたイベントが最初に発生するとは限りません。これについては後ほど説明します。
3 · バブリングフェーズ: 手順はキャプチャフェーズとは逆になり、ウィンドウ内の 2 つのプロパティに段階的にイベントをバブリングします
: e.target と e.currentTarget
target和currentTarget都是event上面的属性,target是真正发生事件的DOM元素,而currentTarget是当前事件发生在哪个DOM元素上。
可以结合控制台打印出来的信息理解下,目标阶段也就是 target == currentTarget的时候。我没有打印它们两个因为太长了,所以打印了它们的nodeName,但是由于window没有nodeName这个属性,所以是undefined。
那可能有一个疑问,我们不用addEventListener绑定的事件会发生在哪个阶段呢,我们来一个测试,顺便再演示一下我在上面的目标阶段所说的目标阶段并不一定先发生捕获阶段所绑定的事件是怎么一回事。
<script> var wrapp = document.getElementById("wrapp"); var innerP = document.getElementById("innerP"); var textSpan = document.getElementById("textSpan"); // 测试直接绑定的事件到底发生在哪个阶段 wrapp.onclick = function () { console.log("wrapp onclick 测试直接绑定的事件到底发生在哪个阶段") }; // 捕获阶段绑定事件 window.addEventListener("click", function (e) { console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.addEventListener("click", function (e) { console.log("document 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.documentElement.addEventListener("click", function (e) { console.log("documentElement 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.body.addEventListener("click", function (e) { console.log("body 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); wrapp.addEventListener("click", function (e) { console.log("wrapp 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); innerP.addEventListener("click", function (e) { console.log("innerP 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); textSpan.addEventListener("click", function () { console.log("textSpan 冒泡 在捕获之前绑定的") }, false); textSpan.onclick = function () { console.log("textSpan onclick") }; textSpan.addEventListener("click", function (e) { console.log("textSpan 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); // 冒泡阶段绑定的事件 window.addEventListener("click", function (e) { console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.addEventListener("click", function (e) { console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.documentElement.addEventListener("click", function (e) { console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.body.addEventListener("click", function (e) { console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); wrapp.addEventListener("click", function (e) { console.log("wrapp 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); innerP.addEventListener("click", function (e) { console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); textSpan.addEventListener("click", function (e) { console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false);</script>
控制台打印如下:
1· textSpan是被点击的元素,也就是目标元素,所有在textSpan上绑定的事件都会发生在目标阶段,在绑定捕获代码之前写了绑定的冒泡阶段的代码,所以在目标元素上就不会遵守先发生捕获后发生冒泡这一规则,而是先绑定的事件先发生。[在目标元素上就不会遵守先发生捕获后发生冒泡这一规则,而是先绑定的事件先发生]
2· 由于wrapp不是目标元素,所以它上面绑定的事件会遵守先发生捕获后发生冒泡的规则。所以很明显用onclick直接绑定的事件发生在了冒泡阶段。
说一下事件绑定、解绑还有阻止事件默认行为:
事件绑定:
1、直接获取元素绑定:
element.onclick = function(e){ // ... };
该方法的优点是:简单和稳定,可以确保它在你使用的不同浏览器中运作一致;处理事件时,this关键字引用的是当前元素,这很有帮组。
缺点:只会在事件冒泡中运行;一个元素一次只能绑定一个事件处理函数,新绑定的事件处理函数会覆盖旧的事件处理函数;事件对象参数(e)仅非IE浏览器可用
2、直接在元素里面使用事件属性
3、W3C方法:
element.onclick = function(e){ // ... };
优点:该方法同时支持事件处理的捕获和冒泡阶段;事件阶段取决于addEventListener最后的参数设置:false (冒泡) 或 true (捕获);在事件处理函数内部,this关键字引用当前元素;事件对象总是可以通过处理函数的第一个参数(e)捕获;可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件
缺点:IE不支持,你必须使用IE的attachEvent函数替代。
IE下的方法:
element.attachEvent('onclick', function(){ // ... });
优点:可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件。
缺点:IE仅支持事件捕获的冒泡阶段;事件监听函数内的this关键字指向了window对象,而不是当前元素(IE的一个巨大缺点);事件对象仅存在与window.event参数中;事件必须以ontype的形式命名,比如,onclick而非click;仅IE可用,你必须在非IE浏览器中使用W3C的addEventListener
注意:不是意味这低版本的ie没有事件捕获,它也是先发生事件捕获,再发生事件冒泡,只不过这个过程无法通过程序控制。
解除事件:
element.removeEventListener('click', function(e){ // ... }, false);
IE:
element.detachEvent('onclick', function(){ // ... });
阻止事件传播
在支持addEventListener()的浏览器中,可以调用事件对象的stopPropagation()方法以阻止事件的继续传播。如果在同一对象上定义了其他处理程序,剩下的处理程序将依旧被调用,但调用stopPropagation()之后任何其他对象上的事件处理程序将不会被调用。不仅可以阻止事件在冒泡阶段的传播,还能阻止事件在捕获阶段的传播。
IE9之前的IE不支持stopPropagation()方法,而是设置事件对象cancelBubble属性为true来实现阻止事件进一步传播。
<script> var wrapp = document.getElementById("wrapp"); var innerP = document.getElementById("innerP"); var textSpan = document.getElementById("textSpan"); // 测试直接绑定的事件到底发生在哪个阶段 wrapp.onclick = function () { console.log("wrapp onclick 测试直接绑定的事件到底发生在哪个阶段") }; // 捕获阶段绑定事件 window.addEventListener("click", function (e) { console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.addEventListener("click", function (e) { console.log("document 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.documentElement.addEventListener("click", function (e) { console.log("documentElement 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.body.addEventListener("click", function (e) { console.log("body 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); wrapp.addEventListener("click", function (e) { console.log("wrapp 捕获", e.target.nodeName, e.currentTarget.nodeName); // 在捕获阶段阻止事件的传播 e.stopPropagation(); }, true); innerP.addEventListener("click", function (e) { console.log("innerP 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); textSpan.addEventListener("click", function () { console.log("textSpan 冒泡 在捕获之前绑定的") }, false); textSpan.onclick = function () { console.log("textSpan onclick") }; textSpan.addEventListener("click", function (e) { console.log("textSpan 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); // 冒泡阶段绑定的事件 window.addEventListener("click", function (e) { console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.addEventListener("click", function (e) { console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.documentElement.addEventListener("click", function (e) { console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.body.addEventListener("click", function (e) { console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); wrapp.addEventListener("click", function (e) { console.log("wrapp 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); innerP.addEventListener("click", function (e) { console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); textSpan.addEventListener("click", function (e) { console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false);</script>
实际上我们点击的是textSpan,但是由于在捕获阶段事件就被阻止了传播,所以在textSpan上绑定的事件根本就没有发生,冒泡阶段绑定的事件自然也不会发生,因为阻止事件在捕获阶段传播的特性,e.stopPropagation()很少用到在捕获阶段去阻止事件的传播,大家就以为e.stopPropagation()只能阻止事件在冒泡阶段传播。
阻止事件的默认行为
e.preventDefault()可以阻止事件的默认行为发生,默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为。
IE9之前的IE中,可以通过设置事件对象的returnValue属性为false达到同样的效果。
(function(){ var color_list = document.getElementById('color-list'); var colors = color_list.getElementsByTagName('li'); for(var i=0;i<colors.length;i++){ colors[i].addEventListener('click',showColor,false); }; function showColor(e){ var x = e.target; alert("The color is " + x.innerHTML); }; })();
事件委托:
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次。也就是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
<ul id="color-list"> <li>red</li> <li>yellow</li> <li>blue</li> <li>green</li> <li>black</li> <li>white</li> </ul>
如果点击页面中的li元素,然后输出li当中的颜色,我们通常会这样写:
(function(){ var color_list = document.getElementById('color-list'); var colors = color_list.getElementsByTagName('li'); for(var i=0;i<colors.length;i++){ colors[i].addEventListener('click',showColor,false); }; function showColor(e){ var x = e.target; alert("The color is " + x.innerHTML); }; })();
利用事件流的特性,我们只绑定一个事件处理函数也可以完成:
(function(){ var color_list = document.getElementById('color-list'); color_list.addEventListener('click',showColor,false); function showColor(e){ var x = e.target; if(x.nodeName.toLowerCase() === 'li'){ alert('The color is ' + x.innerHTML); } } })();
冒泡还是捕获?
对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型。
事件委托还有一个好处就是添加进来的元素也能绑定事件:
没有使用事件委托:
<body> <ul id="thl"> <li>001</li> <li>002</li> <li>003</li> </ul> <button onclick="fun()">touch</button> <script> var thl = document.getElementById('thl'); var aLi = thl.getElementsByTagName('li'); for (var i = 0; i < aLi.length; i++) { aLi[i].onclick = fn; } function fn() { console.log(this.innerHTML); } function fun() { var node = document.createElement("li"); var textnode = document.createTextNode("maomaoliang"); node.appendChild(textnode); document.getElementById("thl").appendChild(node); }</script> </body>
使用了事件委托:
<script> var thl = document.getElementById('thl'); thl.onclick = function (ev) { ev = ev || event; //兼容处理 var target = ev.target || ev.srcElement; //找到li元素 if (target.nodeName.toLowerCase() == 'li') { console.log(target.innerHTML); } }; function fun() { var node = document.createElement("li"); var textnode = document.createTextNode("maomaoliang"); node.appendChild(textnode); document.getElementById("thl").appendChild(node); }</script>
以上就是本篇文章的内容,大家对JavaScript的不太熟悉的可以多看看哦!
相关推荐:
以上がJSのイベントバブリング、イベントキャプチャ、イベント委任とは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。