事件冒泡和捕获

针对 DOM 元素触发的事件不仅会影响它们所定位的元素。DOM 中任何目标的祖先也可能有机会对事件作出反应。请考虑以下文档:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
  <p id="paragraph">
    <span id="text">Hello World</span>
  </p>
</body>
</html>

如果我们只是在没有任何选项的情况下为每个元素添加侦听器,那么触发对 span 的单击…

document.body.addEventListener('click', function(event) {
  console.log("Body clicked!");
});
window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph clicked!");
});
window.text.addEventListener('click', function(event) {
  console.log("Text clicked!");
});

window.text.click();

…然后事件将通过每个祖先冒泡,在途中触发每个点击处理程序:

Text clicked!
Paragraph clicked!
Body clicked!

如果你希望其中一个处理程序阻止事件触发更多处理程序,则可以调用 event.stopPropagation() 方法。例如,如果我们用这个替换第二个事件处理程序:

window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph clicked, and that's it!");
  event.stopPropagation();
});

我们会看到以下输出,bodyclick 处理程序从未触发:

Text clicked!
Paragraph clicked, and that's it!

最后,我们可以选择添加在捕获 期间触发而不是冒泡的事件侦听器。在事件从元素通过其祖先冒泡之前,它首先通过其祖先捕获到元素。通过将 true{capture: true} 指定为 addEventListener 的可选第三个参数来添加捕获侦听器。如果我们在上面的第一个例子中添加以下监听器:

document.body.addEventListener('click', function(event) {
  console.log("Body click captured!");
}, true);
window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph click captured!");
}, true);
window.text.addEventListener('click', function(event) {
  console.log("Text click captured!");
}, true);

我们将得到以下输出:

Body click captured!
Paragraph click captured!
Text click captured!
Text clicked!
Paragraph clicked!
Body clicked!

默认情况下,在冒泡阶段会侦听事件。要更改此设置,你可以通过在 addEventListener 函数中指定第三个参数来指定事件被侦听的阶段。 (要了解捕获和冒泡,请查看备注

element.addEventListener(eventName, eventHandler, useCapture)

useCapture:true 表示当它沿着 DOM 树向下时监听事件。false 意味着在它上升 DOM 树时收听事件。

window.addEventListener("click", function(){alert('1: on bubble')}, false);
window.addEventListener("click", function(){alert('2: on capture')}, true);

警报框将按以下顺序弹出:

  • 2:捕获
  • 1:关于泡沫

真实世界的用例

Capture Event 将在 Bubble Event 之前发送,因此,如果你在捕获阶段收听事件,则可以确保首先收听事件。

如果你正在侦听父元素上的 click 事件以及其子元素上的另一个事件,则可以先侦听子节点,也可以先侦听父节点,具体取决于你更改 useCapture 参数的方式。

StackOverflow 文档

在冒泡中,子事件首先被调用,在捕获中,父项首先被调用

HTML:

<div id="parent">
   <div id="child"></div>
</div>

使用 Javascript:

child.addEventListener('click', function(e) {
   alert('child clicked!');
});

parent.addEventListener('click', function(e) {
   alert('parent clicked!');
}, true);

将 true 设置为父 eventListener 将首先触发父侦听器。

结合 e.stopPropagation(),你可以阻止事件触发子事件侦听器/或父事件。 (更多关于下一个例子中的内容)