深入理解JavaScript事件流:捕获、冒泡与DOM绑定机制
本文详细解析JavaScript中的事件流机制,涵盖事件捕获与冒泡两种核心模式。通过对比HTML内联绑定、DOM0级以及DOM2级addEventListener三种绑定方式,阐明不同方法对事件执行顺序的影响。结合具体代码示例与经典面试题,帮助开发者掌握事件在不同DOM节点间的传递规律,从而精准控制事件处理逻辑。
DOM事件流
要理解事件流的概念,首先需要掌握三个核心要点:
- 页面中的元素并非孤立存在,而是通过层级关系相互连接。
- 当单个元素触发特定事件时,该事件可能会波及并影响其祖先或后代元素。
- 事件流的传播机制主要分为两种:由网景公司提出的事件捕获(Event Capturing)和由IE浏览器提出的事件冒泡(Event Bubbling)。

以常见的DOM结构为例,假设我们为div元素绑定了点击事件。当用户点击该div时,事件并不会局限于该元素,而是会沿着DOM树结构向上或向下传播,产生连锁反应,即所谓的“牵一发而动全身”。
事件流的传播方式主要包含两种机制:事件冒泡和事件捕获。
事件冒泡

在事件冒泡阶段,当点击div后,系统会立即触发目标元素上的事件处理程序,随后事件会逐层向上冒泡,依次触发其父级、祖父级等祖先元素上绑定的事件。
事件捕获

在事件捕获阶段,当点击div后,系统不会立即触发div自身的事件,而是从DOM树的最顶层(如document或window)开始,逐层向下查找,直到到达目标元素div时才触发其事件处理程序。
在实际项目开发中,这两种阶段均可能出现。具体采用哪种阶段,取决于事件绑定的方式以及配置参数。
情景一:直接在HTML标签中绑定事件
<body onclick="console.log('我是body')">
<button onclick="console.log('我是button')">
<span onclick="console.log('我是span')">快乐按钮</span>
</button>
</body>
执行效果如下:

从上述示例可以看出,当我们点击span标签时,事件会按照冒泡的顺序依次执行,即先触发span自身的事件,随后向上触发button身上的事件,最后触发body身上的事件。
情景二:通过[domNode].onclick()方式绑定 —— 即DOM0级事件绑定
HTML结构如下:
<body>
<button>
<span>快乐按钮</span>
</button>
</body>
JavaScript代码如下:
var body = document.getElementsByTagName('body')[0];
var button = document.getElementsByTagName('button')[0];
var span = document.getElementsByTagName('span')[0];
body.onclick = function() {console.log('我是body')};
button.onclick = function() {console.log('我是button')};
span.onclick = function() {console.log('我是span')};
执行效果如下:

通过观察可以发现,采用DOM0级方式绑定事件时,事件的传播形式依然遵循冒泡机制。
情景三:通过[domNode].addEventListener()方式绑定 —— 即DOM2级事件绑定
addEventListener方法的语法结构为:addEventListener(事件名,事件触发后的回调函数,布尔值)。
其中,第三个参数为布尔值:false(默认值)表示在冒泡阶段调用事件处理程序;true表示在捕获阶段调用事件处理程序。
HTML结构如下:
<body>
<button>
<span>快乐按钮</span>
</button>
</body>
JavaScript代码如下:
var body = document.getElementsByTagName('body')[0];
var button = document.getElementsByTagName('button')[0];
var span = document.getElementsByTagName('span')[0];
function theName() {console.log('我是' + this.nodeName)};
body.addEventListener('click',theName,false);
button.addEventListener('click',theName,false);
span.addEventListener('click',theName,false);
执行效果如下:

从结果可以看出,当addEventListener的第三个参数为false(若不写该参数,默认即为false)时,事件流的传播方式为冒泡。
若我们将第三个参数修改为true:
......
body.addEventListener('click',theName,true);
button.addEventListener('click',theName,true);
span.addEventListener('click',theName,true);

此时执行顺序变为body ——> button ——> span,这表明事件处于捕获阶段。
根据DOM2级规范:
- 完整的事件流包括三个阶段:1. 捕获阶段;2. 目标阶段;3. 冒泡阶段。
执行循序应严格遵循以下顺序:捕获阶段 ——> 目标阶段 ——> 冒泡阶段。
这意味着,当用户点击目标元素后,事件不会立即执行目标元素的处理程序,而是先经历事件捕获阶段(从上至下),然后进入目标阶段(触发目标元素事件),最后进入事件冒泡阶段(从下至上)。
接下来,我们分析一道经典的面试题:当点击名为“baby”的婴儿元素时,控制台输出的顺序是什么?

HTML结构如下:
<body>
<div class="grandma">grandma奶奶
<div class="mother">mother妈妈
<div class="daughter">daughter
<div class="baby">baby婴儿</div>
</div>
</div>
</div>
</body>
JavaScript代码如下:
var grandma = document.getElementsByClassName('grandma')[0]
var mother = document.getElementsByClassName('mother')[0]
var daughter = document.getElementsByClassName('daughter')[0]
var baby = document.getElementsByClassName('baby')[0]
function theName() {
console.log('我是' + this.className);
}
baby.addEventListener('click', theName, false)//冒泡
daughter.addEventListener('click', theName, true)//捕获
mother.addEventListener('click', theName, true)//捕获
grandma.onclick = theName//冒泡
最终输出结果如下:

解题思路如下:
- 首先,我们需要分析已绑定事件的元素属于捕获还是冒泡模式。我们可以看到
daughter和mother被设置为捕获;而baby和grandma被设置为冒泡。 - 根据DOM2级规范,事件流先经过捕获阶段,后经过冒泡阶段。因此,在捕获阶段会依次打印daughter和mother中的某一个(取决于谁先被捕获,实际上两者都在捕获阶段,按DOM树顺序执行),而在冒泡阶段会打印baby和grandma。
- 由于捕获阶段是从DOM树根节点向下传播(dom根——>div),因此会先执行mother的捕获逻辑,随后执行daughter的捕获逻辑,即先打印mother后打印daughter。
- 由于冒泡阶段是从目标节点向上传播(div——>dom根),因此会在点击baby后,先执行baby自身的冒泡逻辑(若绑定),随后向上打印grandma。
总结
本文内容到此结束,希望能为读者带来帮助。如果您觉得有用,欢迎持续关注本站以获取更多技术干货!
AOS.js 动画不触发怎么办?排查 DOM 加载与移动端兼容问题
2026-11-05 17:02:26
sigma.js 怎么实现节点点击、拖拽与悬停提示交互
2026-11-05 15:03:27
Matter.js 与 PixiJS/Three.js 集成:分离物理计算与渲染
2026-11-05 13:43:48
主流JS弹幕库选型指南:Danmaku.js等库的功能与体积对比
2026-11-05 09:28:19
JavaScript点击事件监听与交互效果实例
2026-11-05 09:10:19
