47. JS事件冒泡与事件捕获
1. 前言
在 JavaScript 中,我们将事件发生的顺序称为“事件流”,当我们触发某个事件时,会发生一些列的连锁反应,例如有如下所示的一段代码:
1 2 3 4 5 6 7 | <body> <div id= "wrap" > <p class = "hint" > <a href= "#" >Click Me</a> </p> </div> </body> |
如果给每个标签都定义事件,当我们点击其中的<a>
标签时,会发现绑定在<div>
和<p>
标签上的事件也被触发了,这到底是为什么呢?为了解答这一问题,微软和网景两公司提出了两种不同的概念,事件捕获与事件冒泡:
- 事件捕获:由微软公司提出,事件从文档根节点(Document 对象)流向目标节点,途中会经过目标节点的各个父级节点,并在这些节点上触发捕获事件,直至到达事件的目标节点;
- 事件冒泡:由网景公司提出,与事件捕获相反,事件会从目标节点流向文档根节点,途中会经过目标节点的各个父级节点,并在这些节点上触发捕获事件,直至到达文档的根节点。整个过程就像水中的气泡一样,从水底向上运动。
提示:上面提到的目标节点指的是触发事件的节点。
后来,W3C 为了统一标准,采用了一个折中的方式,即将事件捕获与事件冒泡合并,也就是现在的“先捕获后冒泡”,如下图所示:

图:事件捕获与事件冒泡
2. 事件捕获
在事件捕获阶段,事件会从 DOM 树的最外层开始,依次经过目标节点的各个父节点,并触发父节点上的事件,直至到达事件的目标节点。以上图中的代码为例,如果单击其中的<a>
标签,则该事件将通过document -> div -> p -> a
的顺序传递到<a>
标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>JavaScript</title> <style type= "text/css" > div, p, a { padding: 15px 30px; display: block; border: 2px solid #000; background: #fff; } </style> </head> <body> <div id= "wrap" >DIV <p class = "hint" >P <a href= "#" >A</a> </p> </div> <script> function showTagName() { alert( "事件捕获: " + this .tagName); } var elems = document.querySelectorAll( "div, p, a" ); for ( let elem of elems) { elem.addEventListener( "click" , showTagName, true ); } </script> </body> </html> |
运行上面的代码,单击最内层的<a>
标签,运行结果如下图所示:

图:事件捕获演示
3. 事件冒泡
事件冒泡正好与事件捕获相反,事件冒泡是从目标节点开始,沿父节点依次向上,并触发父节点上的事件,直至文档根节点,就像水底的气泡一样,会一直向上。
示例代码如下:
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>JavaScript</title> <style type= "text/css" > div, p, a { padding: 15px 30px; display: block; border: 2px solid #000; background: #fff; } </style> </head> <body> <div onclick= "alert('事件冒泡: ' + this.tagName)" >DIV <p onclick= "alert('事件冒泡: ' + this.tagName)" >P <a href= "#" onclick= "alert('事件冒泡: ' + this.tagName)" >A</a> </p> </div> </body> </html> |
运行上面的代码,单击最内层的<a>
标签,运行结果如下图所示:

图:事件冒泡演示
4. 阻止事件捕获和冒泡
了解了事件捕获和事件冒泡后会发现,这个特性并不友好,例如我们在某个节点上绑定了事件,本想在点击时触发这个事件,结果由于事件冒泡,这个节点的事件被它的子元素给触发了。我们要如何阻止这样的事情发生呢?
JavaScript 中提供了 stopPropagation() 方法来阻止事件捕获和事件冒泡的发生,语法格式如下:
1 | event .stopPropagation(); |
注意:stopPropagation() 会阻止事件捕获和事件冒泡,但是无法阻止标签的默认行为,例如点击链接任然可以打开对应网页。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>JavaScript</title> <style type= "text/css" > div, p, a { padding: 15px 30px; display: block; border: 2px solid #000; background: #fff; } </style> </head> <body> <div id= "wrap" >DIV <p class = "hint" >P <a href= "#" >A</a> </p> </div> <script> function showAlert( event ) { alert( "您点击了 " + this .tagName + " 标签" ); event .stopPropagation(); } var elems = document.querySelectorAll( "div, p, a" ); for ( let elem of elems) { elem.addEventListener( "click" , showAlert); } </script> </body> </html> |
此外,您也可以使用 stopImmediatePropagation() 方法来阻止同一节点的同一事件的其它事件处理程序,例如为某个节点定义了多个点击事件,当事件触发时,这些事件会按定义顺序依次执行,如果其中一个事件处理程序中使用了 stopImmediatePropagation() 方法,那么剩下的事件处理程序将不再执行。
stopImmediatePropagation() 方法的语法格式如下:
1 | event .stopImmediatePropagation(); |
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>JavaScript</title> <style type= "text/css" > div, p, a { padding: 15px 30px; display: block; border: 2px solid #000; background: #fff; } </style> </head> <body> <div onclick= "alert('您点击了 ' + this.tagName + ' 标签')" >DIV <p onclick= "alert('您点击了 ' + this.tagName + ' 标签')" >P <a href= "#" id= "link" >A</a> </p> </div> <script> function sayHi() { alert( "事件处理程序 1" ); event .stopImmediatePropagation(); } function sayHello() { alert( "事件处理程序 2" ); } // 为 id 为 link 的标签定义多个点击事件 var link = document.getElementById( "link" ); link.addEventListener( "click" , sayHi); link.addEventListener( "click" , sayHello); </script> </body> </html> |
5. 阻止默认操作
某些事件具有与之关联的默认操作,例如当您单击某个链接时,会自动跳转到指定的页面,当您单击提交按钮时,会将数据提交到服务器等。如果不想这样的默认操作发生,可以使用 preventDefault() 方法来阻止,其语法格式如下:
1 | event .preventDefault(); |
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>JavaScript</title> </head> <body> <a href= "http://c.biancheng.net/" id= "link" >链接</a> <script> var link = document.getElementById( "link" ); link.addEventListener( 'click' , function( event ){ event .preventDefault(); // 阻止链接跳转 }); </script> </body> </html> |
注意:IE9 及以下的版本不支持 preventDefault() 方法,对于 IE9 及以下的浏览器您可以使用 event.returnValue = false;
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了