什么是事件冒泡和捕获?

事件冒泡和捕获有什么区别?什么时候应该使用冒泡和捕获?

540791 次浏览

问题描述

quirksmode.org对此有很好的描述。简而言之(从quirksmode复制):

事件捕获

当您使用事件捕获时

               | |---------------| |-----------------| element1     | |                ||   -----------| |-----------     ||   |element2  \ /          |     ||   -------------------------     ||        Event CAPTURING          |-----------------------------------

element1的事件处理程序首先触发,element2的事件处理程序最后触发。

事件冒泡

当你使用事件冒泡

               / \---------------| |-----------------| element1     | |                ||   -----------| |-----------     ||   |element2  | |          |     ||   -------------------------     ||        Event BUBBLING           |-----------------------------------

element2的事件处理程序首先触发,element1的事件处理程序最后触发。


使用什么?

这取决于你想做什么。没有更好的了。不同之处在于事件处理程序的执行顺序。大多数情况下,在冒泡阶段触发事件处理程序是可以的,但也可能需要更早地触发它们。

事件冒泡和捕获是超文本标记语言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元素处理。

有关更多信息,请参阅

在下面的示例中,如果单击任何突出显示的元素,您可以看到事件传播流的捕获阶段首先发生,然后是冒泡阶段。

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>

JSFiddle的另一个例子

如果有两个元素元素1和元素2。元素2在元素1内部,我们用这两个元素附加一个事件处理程序,比如onClick。现在当我们单击元素2时,两个元素的事件处理程序都将被执行。现在这里的问题是事件将按哪个顺序执行。如果与元素1附加的事件先执行,则称为事件捕获,如果与元素2附加的事件先执行,则称为事件冒泡。根据W3C,事件将在捕获阶段开始,直到它到达目标返回到元素,然后开始冒泡

捕获和冒泡状态通过addEventListener方法的useCapture参数已知

eventTarget.addEventListener(type, listener,[, 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>

请尝试更改true和false。

我发现这个javascript.info教程在解释这个主题时非常清楚。它最后的3点总结确实是在谈论关键点。我在这里引用它:

  1. 事件首先被捕获到最深的目标,然后冒泡。在IE<9他们只冒泡。
  2. 所有处理程序都在冒泡阶段例外上工作addEventListener和最后一个参数true,这是唯一的方法在捕获阶段捕获事件。
  3. 冒泡/捕获可以停止event.cancelBubble=true(IE)或event.stopPropagation()对于其他浏览器。

还有#0属性可以告诉您事件是在目标位置还是来自其他地方,并且浏览器完全支持它。

扩展已经伟大的片段从接受的答案,这是使用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_PHASEreturn "Event is being propagated through the target's ancestor objects";case 2: //Event.AT_TARGETreturn "The event has arrived at the event's target";case 3: //Event.BUBBLING_PHASEreturn "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的捕获将比冒泡先执行;而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 bubblecapture(1);capture(2);bubble(2);bubble(1);
// try reverse orderbubble(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上)。尽管如此,我认为应该意识到这一点。

DOM事件描述了事件传播的3个阶段:捕获阶段-事件向下传递到元素。目标阶段-事件到达目标元素。冒泡阶段-事件从元素冒泡。

在此处输入图片描述