事件冒泡和捕获之间有什么区别?何时应该使用冒泡而不是捕获?
事件冒泡和捕获之间有什么区别?何时应该使用冒泡而不是捕获?
早期,Netscape倡导事件捕获,而Microsoft则推广事件冒泡。两者都是W3C 文档对象模型事件标准(2000)的一部分。自上而下,自下而上
addEventListener(type, listener, useCapture)
在冒泡(默认)或捕获模式下注册事件处理程序。要使用捕获模式,请将第三个参数设置为true
。
<div>
<ul>
<li></li>
</ul>
</div>
li
元素中发生了点击事件。div
处理(div
中的点击事件处理程序将首先触发),然后在ul
中处理,最后在目标元素li
中处理。li
处理,然后由ul
处理,最后由div
元素处理。var logElement = document.getElementById('log');
function log(msg) {
logElement.innerHTML += ('<p>' + msg + '</p>');
}
function capture() {
log('capture: ' + this.firstChild.nodeValue.trim());
}
function bubble() {
log('bubble: ' + this.firstChild.nodeValue.trim());
}
function clearOutput() {
logElement.innerHTML = "";
}
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', capture, true);
divs[i].addEventListener('click', bubble, false);
}
var clearButton = document.getElementById('clear');
clearButton.addEventListener('click', clearOutput);
p {
line-height: 0;
}
div {
display:inline-block;
padding: 5px;
background: #fff;
border: 1px solid #aaa;
cursor: pointer;
}
div:hover {
border: 1px solid #faa;
background: #fdd;
}
<div>1
<div>2
<div>3
<div>4
<div>5</div>
</div>
</div>
</div>
</div>
<button id="clear">clear output</button>
<section id="log"></section>
向下滴漏
=> onElement
=> 泡沫上升
。 - runspiredfocus
)。 - thdoanDescription:
quirksmode.org 提供了一个很好的描述。简而言之(从quirksmode复制):
事件捕获
当您使用事件捕获时
| | ---------------| |----------------- | element1 | | | | -----------| |----------- | | |element2 \ / | | | ------------------------- | | Event CAPTURING | -----------------------------------
元素1的事件处理程序首先触发,元素2的事件处理程序最后触发。
事件冒泡
当您使用事件冒泡时
/ \ ---------------| |----------------- | element1 | | | | -----------| |----------- | | |element2 | | | | | ------------------------- | | Event BUBBLING | -----------------------------------
元素2的事件处理程序首先触发,元素1的事件处理程序最后触发。
要使用什么?
这取决于您想要做什么。没有更好的选择。差异在于事件处理程序执行的顺序。大多数情况下,在冒泡阶段触发事件处理程序是可以接受的,但有时需要更早地触发它们。
默认情况下,useCapture为false,即处于冒泡阶段。eventTarget.addEventListener(type,listener,[,useCapture]);
var div1 = document.querySelector("#div1");
var div2 = document.querySelector("#div2");
div1.addEventListener("click", function (event) {
alert("you clicked on div 1");
}, true);
div2.addEventListener("click", function (event) {
alert("you clicked on div 2");
}, false);
#div1{
background-color:red;
padding: 24px;
}
#div2{
background-color:green;
}
<div id="div1">
div 1
<div id="div2">
div 2
</div>
</div>
请尝试更改true和false。
useCapture
,可以设置为true或false;而在HTML 4.0中,事件侦听器被指定为元素的属性,并且useCapture
默认为false。您能否提供一个链接到确认您所写内容的规范? - surfmuggle我发现这个javascript.info的教程在解释这个主题时非常清晰。它在结尾处的三点总结真正涉及到了关键点。我在这里引用:
- 事件首先被捕获到最深的目标,然后冒泡上升。在IE<9中,它们只会冒泡。
- 除了使用最后一个参数
true
的addEventListener
之外,所有处理程序都在冒泡阶段工作,这是唯一可以在捕获阶段捕获事件的方法。- 冒泡/捕获可以通过
event.cancelBubble=true
(IE)或event.stopPropagation()
(其他浏览器)停止。
还有一个 Event.eventPhase
属性,可以告诉你事件是在目标阶段还是来自其他地方,并且它得到了浏览器的完全支持。
基于已经接受答案中的精彩代码片段,这是使用eventPhase
属性的输出结果。
var logElement = document.getElementById('log');
function log(msg) {
if (logElement.innerHTML == "<p>No logs</p>")
logElement.innerHTML = "";
logElement.innerHTML += ('<p>' + msg + '</p>');
}
function humanizeEvent(eventPhase){
switch(eventPhase){
case 1: //Event.CAPTURING_PHASE
return "Event is being propagated through the target's ancestor objects";
case 2: //Event.AT_TARGET
return "The event has arrived at the event's target";
case 3: //Event.BUBBLING_PHASE
return "The event is propagating back up through the target's ancestors in reverse order";
}
}
function capture(e) {
log('capture: ' + this.firstChild.nodeValue.trim() + "; " +
humanizeEvent(e.eventPhase));
}
function bubble(e) {
log('bubble: ' + this.firstChild.nodeValue.trim() + "; " +
humanizeEvent(e.eventPhase));
}
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', capture, true);
divs[i].addEventListener('click', bubble, false);
}
p {
line-height: 0;
}
div {
display:inline-block;
padding: 5px;
background: #fff;
border: 1px solid #aaa;
cursor: pointer;
}
div:hover {
border: 1px solid #faa;
background: #fdd;
}
<div>1
<div>2
<div>3
<div>4
<div>5</div>
</div>
</div>
</div>
</div>
<button onclick="document.getElementById('log').innerHTML = '<p>No logs</p>';">Clear logs</button>
<section id="log"></section>
冒泡排序
Event propagate to the upto root element is **BUBBLING**.
捕获
Event propagate from body(root) element to eventTriggered Element is **CAPTURING**.
<body>
<div>
<button>click</button>
</div>
</body>
浏览器会查看刚刚点击的元素,然后它将转到最上层的父元素,即 body
。如果在 body 中有任何点击处理程序,浏览器将调用它。在检查了 body 元素之后,它将查看第二个顶级父元素,即 div
元素。这个过程将重复,直到浏览器到达最底部的按钮元素。一旦它看到按钮元素,第一阶段 - 捕获就结束了。大多数情况下,我们忽略此阶段。此代码忽略此阶段。
document.addEventListener('click',handleClick)
document.addEventListener('click',handleClick,true)
按钮
,如果该按钮具有事件处理程序,则会调用它。捕获阶段
相反,在此阶段中,浏览器将从立即父元素开始处理,即在此情况下为div
元素,然后它将访问body
元素。此代码将为冒泡阶段设置事件处理程序。document.addEventListener('click',handleClick,false)
我做了一个小例子,你可以在其中实时体验“事件冒泡”:https://codepen.io/abernier/pen/yKGJXK?editors=1010
点击任何 div,您将看到“click”事件冒泡!$divs.forEach(($div) => $div.addEventListener("click", handleClick2));
正如其他人所说,冒泡和捕获描述了一些嵌套元素接收给定事件的顺序。
我想指出的是,对于最内层的元素可能会出现一些奇怪的情况。实际上,对于某些浏览器(例如Mozilla Firefox),添加事件监听器的顺序确实很重要。
在下面的示例中,div2
的捕获将先于冒泡执行;而div4
的冒泡将先于捕获执行。
function addClickListener (msg, num, type) {
document.querySelector("#div" + num)
.addEventListener("click", () => alert(msg + num), type);
}
bubble = (num) => addClickListener("bubble ", num, false);
capture = (num) => addClickListener("capture ", num, true);
// first capture then bubble
capture(1);
capture(2);
bubble(2);
bubble(1);
// try reverse order
bubble(3);
bubble(4);
capture(4);
capture(3);
#div1, #div2, #div3, #div4 {
border: solid 1px;
padding: 3px;
margin: 3px;
}
<div id="div1">
div 1
<div id="div2">
div 2
</div>
</div>
<div id="div3">
div 3
<div id="div4">
div 4
</div>
</div>
备注: 请尝试使用Mozilla Firefox运行上面的代码片段。
capture(3); capture(4); bubble(4); bubble(3);
,然后再次点击 div #4,你会得到“capture 3, capture 4, bubble 4, bubble 3”。这是一个事实,尽管我无法解释它。 - logi-kalcapture(3); capture(4); bubble(4); bubble(3)
。 - sasidhar