事件冒泡和捕获有什么区别?什么时候应该使用冒泡与捕获?
8 回答
事件冒泡和捕获是 HTML DOM API 中事件传播的两种方式,当事件发生在另一个元素内的元素中,并且两个元素都为该事件注册了句柄时。事件传播模式确定元素接收事件的顺序。
通过冒泡,事件首先被最里面的元素捕获和处理,然后传播到外部元素。
通过捕获,事件首先被最外层元素捕获并传播到内部元素。
捕获也称为“trickling”,它有助于记住传播顺序:
涓涓细流,冒泡
过去,Netscape 提倡事件捕获,而 Microsoft 提倡事件冒泡。两者都是 W3C文档对象模型事件标准 (2000) 的一部分。
IE < 9仅使用事件冒泡,而 IE9+ 和所有主流浏览器都支持这两者。另一方面,对于复杂的 DOM ,事件冒泡的性能可能会略低一些。
我们可以使用addEventListener(type, listener, useCapture)
为冒泡(默认)或捕获模式注册事件处理程序。要使用捕获模型,请将第三个参数作为true
.
例子
<div>
<ul>
<li></li>
</ul>
</div>
li
在上面的结构中,假设元素中发生了点击事件。
在捕获模型中,事件将由div
第一个处理(单击事件处理程序div
将首先触发),然后在 中ul
,最后在目标元素中,li
.
在冒泡模型中,会发生相反的情况:事件将首先由 处理li
,然后由处理ul
,最后由div
元素处理。
有关详细信息,请参阅
- QuirksMode 上的事件顺序
- MDN 上的addEventListener
- QuirksMode 上的高级事件
在下面的示例中,如果单击任何突出显示的元素,您可以看到事件传播流的捕获阶段首先发生,然后是冒泡阶段。
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>
描述:
quirksmode.org对此有很好的描述。简而言之(从 quirksmode 复制):
事件捕获
当您使用事件捕获时
| | ---------------| |----------------- | element1 | | | | -----------| |----------- | | |element2 \ / | | | ------------------------- | | Event CAPTURING | -----------------------------------
element1 的事件处理程序首先触发,element2 的事件处理程序最后触发。
事件冒泡
当您使用事件冒泡时
/ \ ---------------| |----------------- | element1 | | | | -----------| |----------- | | |element2 | | | | | ------------------------- | | Event BUBBLING | -----------------------------------
element2 的事件处理程序首先触发,element1 的事件处理程序最后触发。
用什么?
这取决于你想做什么。没有更好的了。不同之处在于事件处理程序的执行顺序。大多数情况下,在冒泡阶段触发事件处理程序会很好,但也可能需要更早地触发它们。
如果有两个元素元素 1 和元素 2。元素 2 在元素 1 内,我们为这两个元素附加一个事件处理程序,比如说 onClick。现在,当我们单击元素 2 时,这两个元素的 eventHandler 都将被执行。现在这里的问题是事件将按什么顺序执行。如果元素 1 附加的事件首先执行,则称为事件捕获,如果元素 2 附加的事件首先执行,则称为事件冒泡。根据 W3C,事件将在捕获阶段开始,直到它到达目标返回到元素,然后开始冒泡
捕获和冒泡状态由 addEventListener 方法的 useCapture 参数知道
eventTarget.addEventListener(type,listener,[,useCapture]);
默认情况下,useCapture 为 false。这意味着它处于冒泡阶段。
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>
请尝试更改真假。
我发现javascript.info上的本教程在解释这个主题时非常清楚。而它最后的3点总结,真的是说到了要害。我在这里引用它:
- 事件首先被捕获到最深的目标,然后冒泡。在 IE<9 中,它们只会冒泡。
- 所有处理程序都在冒泡阶段工作,除了
addEventListener
最后一个参数true
,这是在捕获阶段捕获事件的唯一方法。- 冒泡/捕获可以通过
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**.
正如其他人所说,冒泡和捕获描述了一些嵌套元素接收给定事件的顺序。
我想指出,最里面的元素可能会出现一些奇怪的东西。实际上,在这种情况下,添加事件侦听器的顺序确实很重要。
在下面的例子中,捕获div2
比冒泡先执行;而冒泡 fordiv4
将首先执行而不是捕获。
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>
编辑:这种行为可能因浏览器而异(例如,当前发生在 Firefox 上,但不在 Chrome 和 Edge 上)。尽管如此,我认为人们应该意识到这一点。