JavaScript中的事件流描述的是从页面中接收事件的顺序,而事件冒泡和事件捕获是事件流的两个不同阶段,理解这两个机制是正确处理DOM事件的前提。事件冒泡指的是事件从最具体的元素开始,逐级向上传播到较为不具体的节点,最终到达document对象。事件捕获则相反,事件从最不具体的节点开始,逐级向下传播到最具体的元素。DOM标准规定事件流包含三个阶段:捕获阶段、目标阶段、冒泡阶段。

事件冒泡机制详解
事件冒泡是大多数浏览器默认的事件传播方式,当一个元素触发了某个事件,这个事件会先在该元素上执行对应的事件处理函数,然后会依次在其父元素、祖父元素等上级节点上触发同类型事件,直到传播到document对象为止。我们可以通过一个嵌套结构的示例来验证事件冒泡的效果。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>事件冒泡示例</title>
<style>
.outer {
width: 200px;
height: 200px;
background-color: #f0f0f0;
padding: 20px;
}
.inner {
width: 100px;
height: 100px;
background-color: #cccccc;
}
</style>
</head>
<body>
<div class="outer" id="outer">
外层容器
<div class="inner" id="inner">
内层容器
</div>
</div>
<script>
// 给外层容器绑定点击事件
document.getElementById('outer').addEventListener('click', function() {
console.log('外层容器被点击');
});
// 给内层容器绑定点击事件
document.getElementById('inner').addEventListener('click', function() {
console.log('内层容器被点击');
});
</script>
</body>
</html>
当点击内层容器时,控制台会先输出内层容器被点击,再输出外层容器被点击,这就是事件冒泡的典型表现。如果点击的是外层容器本身,只会触发外层容器的点击事件,不会向上传播。
事件捕获机制详解
事件捕获的执行顺序和事件冒泡完全相反,事件会从最顶层的节点开始,逐级向下传播到触发事件的具体元素。默认情况下,我们使用addEventListener方法绑定事件时,第三个参数默认为false,代表事件在冒泡阶段执行。如果要让事件在捕获阶段执行,只需要把第三个参数设置为true即可。
我们修改上面的示例代码,将外层容器的事件绑定改为捕获阶段执行:
// 给外层容器绑定点击事件,第三个参数为true,代表捕获阶段执行
document.getElementById('outer').addEventListener('click', function() {
console.log('外层容器被点击(捕获阶段)');
}, true);
// 内层容器仍然在冒泡阶段执行
document.getElementById('inner').addEventListener('click', function() {
console.log('内层容器被点击(冒泡阶段)');
});
此时点击内层容器,控制台会先输出外层容器被点击(捕获阶段),再输出内层容器被点击(冒泡阶段),因为捕获阶段先于冒泡阶段执行,事件从外层向内层传播时先触发外层的捕获事件,到达目标元素后,再执行目标元素的冒泡事件,最后向上冒泡。
事件流的完整阶段
DOM事件流完整的执行过程分为三个阶段:
- 捕获阶段:事件从document对象向下传播到目标元素的父节点,这个阶段只会执行绑定在捕获阶段的事件处理函数。
- 目标阶段:事件到达触发事件的目标元素本身,此时会执行目标元素上绑定的所有对应事件处理函数,不分捕获和冒泡。
- 冒泡阶段:事件从目标元素向上传播回document对象,这个阶段只会执行绑定在冒泡阶段的事件处理函数。
如何控制事件传播
在实际开发中,我们可能需要阻止事件的继续传播,比如点击某个元素时只触发该元素的事件,不让父元素也触发。这时候可以使用stopPropagation方法,该方法可以阻止事件继续传播,不管是捕获阶段还是冒泡阶段。
document.getElementById('inner').addEventListener('click', function(e) {
console.log('内层容器被点击');
// 阻止事件继续传播
e.stopPropagation();
});
document.getElementById('outer').addEventListener('click', function() {
console.log('外层容器被点击');
});
此时点击内层容器,只会输出内层容器被点击,外层的点击事件不会被触发,因为事件传播被阻止了。需要注意的是,stopPropagation只能阻止事件的传播,无法阻止同一个元素上其他同类型事件处理函数的执行,如果要阻止同一个元素上的其他事件处理函数,可以使用stopImmediatePropagation方法。
实际开发中的应用场景
事件冒泡和事件捕获在实际开发中有很多应用场景,比如事件委托就是基于事件冒泡实现的。事件委托指的是把子元素的事件委托给父元素来处理,不需要给每个子元素都绑定事件,只需要给父元素绑定一个事件,通过判断事件的目标元素来执行对应的逻辑,这样可以减少事件绑定的数量,提升性能,尤其是处理动态添加的子元素时非常有用。
<ul id="list">
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
</ul>
<script>
// 给ul绑定点击事件,利用事件冒泡处理li的点击
document.getElementById('list').addEventListener('click', function(e) {
// 判断点击的目标是否是li元素
if (e.target.tagName === 'LI') {
console.log('点击了:' + e.target.innerText);
}
});
</script>
而事件捕获的应用场景相对较少,一般在需要在事件到达目标元素之前就拦截处理的时候使用,比如需要优先处理外层容器的事件,再处理内层元素的事件时,可以把外层容器的事件绑定在捕获阶段。
常见误区说明
很多开发者会误以为事件冒泡是从父元素传到子元素,这是错误的,事件冒泡的方向是从内到外。另外,并不是所有的事件都支持冒泡,比如focus、blur、load等事件就不支持冒泡,只有通用的鼠标事件、键盘事件、表单事件等大多支持冒泡。如果需要判断某个事件是否支持冒泡,可以查看对应事件的bubbles属性,该属性为true则表示支持冒泡。
JavaScript事件冒泡事件捕获事件流DOM事件修改时间:2026-06-21 15:54:34