JavaScript 事件冒泡是为了捕捉和处理 DOM 内部传播的事件。但是你知道事件冒泡和事件捕获之间的区别吗?
在这篇文章中,我将用相关的示例来讨论关于这个主题你所需要了解的全部情况。
事件流的传播
在介绍事件捕获和事件冒泡之前,先来看下一个事件是如何在 DOM 内部传播的。
如果我们有几个嵌套的元素处理同一个事件,我们会对哪个事件处理程序会先触发的问题感到困惑。这时,理解事件传播顺序就变得很有必要。
通常,一个事件会从父元素开始向目标元素传播,然后它将被传播回父元素。
JavaScript 事件分为三个阶段:
捕获阶段:事件从父元素开始向目标元素传播,从 Window
对象开始传播。
目标阶段:该事件到达目标元素或开始该事件的元素。
冒泡阶段:这时与捕获阶段相反,事件向父元素传播,直到 Window
对象。
下图将让你进一步了解事件传播的生命周期:
现在你大概了解了 DOM 内部的事件流程,让我们再来看下事件捕获和冒泡是如何出现的。
什么是事件捕获
事件捕获是事件传播的初始场景,从包装元素开始,一直到启动事件生命周期的目标元素。
如果你有一个与浏览器的 Window
对象绑定的事件,它将是第一个被执行的。所以,在下面的例子中,事件处理的顺序将是 Window
、Document
、DIV 2
、DIV 1
,最后是 button
。
这里我们可以看到,事件捕获只发生在被点击的元素或目标上,该事件不会传播到子元素。
我们可以使用 addEventListener()
方法的 useCapture
参数来注册捕捉阶段的事件。
target.addEventListener(type,listener,useCapture)
你可以使用下面的代码来测试上述示例,并获得事件捕获的实践经验。
window.addEventListener("click",()=>{console.log('Window');},true);document.addEventListener("click",()=>{console.log('Document');},true);document.querySelector(".div2").addEventListener("click",()=>{console.log('DIV2');},true);document.querySelector(".div1").addEventListener("click",()=>{console.log('DIV1');},true);document.querySelector("button").addEventListener("click",()=>{console.log('CLICKME!');},true);
什么是事件冒泡
如果你知道事件捕获,事件冒泡就很容易理解,它与事件捕获是完全相反的。
事件冒泡将从一个子元素开始,在 DOM 树上传播,直到最上面的父元素事件被处理。
在 addEventListener()
中省略或将 useCapture
参数设置为 false
,将注册冒泡阶段的事件。所以,事件监听器默认监听冒泡事件。
在我们的示例中,我们对所有的事件使用了事件捕获或事件冒泡。但是如果我们想在两个阶段内都处理事件呢?
让我们举个例子,在冒泡阶段处理 Document
和 DIV 2
的点击事件,其他事件则在捕获阶段处理。
连接到 Window
、DIV 1
和 button
的点击事件将在捕获过程中分别触发,而 DIV 2
和 Document
监听器则在冒泡阶段依次触发。
window.addEventListener("click",()=>{console.log('Window');},true);document.addEventListener("click",()=>{console.log('Document');});//已注册为冒泡document.querySelector(".div2").addEventListener("click",()=>{console.log('DIV2');});//已注册为冒泡document.querySelector(".div1").addEventListener("click",()=>{console.log('DIV1');},true);document.querySelector("button").addEventListener("click",()=>{console.log('CLICKME!');},true);
我想现在你已经对事件流、事件冒泡和事件捕获有了很好的理解。那么,让我们看下什么时候可以使用事件冒泡和事件捕获。
事件捕获和冒泡的应用
通常情况下,我们只需要在全局范围内执行一个函数,就可以使用事件传播。例如,我们可以注册文档范围内的监听器,如果 DOM
内有事件发生,它就会运行。
同样地,我们可以使用事件捕获和冒泡来改变用户界面。
假设我们有一个允许用户选择单元格的表格,我们需要向用户显示所选单元格。
在这种情况下,为每个单元格分配事件处理程序将不是一个好的做法。它最终会导致代码的重复。
作为一个解决方案,我们可以使用一个单独的事件监听器,并利用事件冒泡和捕获来处理这些事件。
因此,我为 table
创建了一个单独的事件监听器,它将被用来改变单元格的样式。
document.querySelector("table").addEventListener("click",(event)=>{if(event.target.nodeName=='TD')event.target.style.background="rgb(230,226,40)";});
在事件监听器中,我使用 nodeName
来匹配被点击的单元格,如果匹配,单元格的颜色就会改变。
如何防止事件传播
有时,如果事件冒泡和捕捉开始不受我们控制地传播时,就会让人感到厌烦。
如果你有一个严重嵌套的元素结构,这也会导致性能问题,因为每个事件都会创建一个新的事件周期。
在上述情况下,当我点击删除按钮时,包装元素的点击事件也被触发了。这是由于事件冒泡导致的。
我们可以使用
stopPropagation()
方法来避免这种行为,它将阻止事件沿着 DOM 树向上或向下进一步传播。
document.querySelector(".card").addEventListener("click",()=>{$("#detailsModal").modal();});document.querySelector("button").addEventListener("click",(event)=>{event.stopPropagation();//停止冒泡$("#deleteModal").modal();});
本文总结
JavaScript 事件捕获和冒泡可以用来有效地处理 Web 应用程序中的事件。了解事件流以及捕获和冒泡是如何工作的,将有助于你通过正确的事件处理来优化你的应用程序。
例如,如果你的应用程序中有任何意外的事件启动,了解事件捕获和冒泡可以节省你排查问题的时间。
因此,我希望你尝试上述示例并在评论区分享你的经验。
感谢阅读!
原文地址:Event Bubbling and Capturing in JavaScript
原文作者:Dulanka Karunasena
译文出自:掘金翻译计划
本文永久链接:https://github.com/xitu/gold-miner/blob/master/article/2021/event-bubbling-and-capturing-in-javascript.md
译者:Z招锦
校对者:KimYangOfCat、jaredliw