JS 事件流机制详解:冒泡、捕获与完整事件流
文章目录
- JS 事件流机制详解:冒泡、捕获与完整事件流
- 一、DOM 事件流基本概念
- 二、事件捕获 (Event Capturing)
- 特点
- 代码示例
- 三、事件冒泡 (Event Bubbling)
- 特点
- 代码示例
- 四、完整事件流示例
- HTML 结构
- JavaScript 代码
- 五、事件流控制方法
- 1. 阻止事件传播
- 2. 阻止默认行为
- 3. 事件委托 (利用冒泡)
- 六、实际应用场景
- 七、注意事项
一、DOM 事件流基本概念
DOM 事件流描述了事件在网页中传播的完整过程,包含三个阶段:
-
捕获阶段 (Capture Phase):从 window 对象向下传播到目标元素
-
目标阶段 (Target Phase):事件到达目标元素
-
冒泡阶段 (Bubble Phase):从目标元素向上传播回 window 对象
二、事件捕获 (Event Capturing)
特点
- 传播方向:从最外层祖先元素向目标元素传播
- 触发顺序:window → document → html → body → … → 目标元素
- 应用场景:需要在事件到达目标前拦截处理
代码示例
// 添加捕获阶段事件监听 (第三个参数为true)
document.querySelector('#outer').addEventListener('click', function() {
console.log('捕获阶段 - Outer');
}, true);
document.querySelector('#inner').addEventListener('click', function() {
console.log('捕获阶段 - Inner');
}, true);
三、事件冒泡 (Event Bubbling)
特点
- 传播方向:从目标元素向最外层祖先元素传播
- 触发顺序:目标元素 → … → body → html → document → window
- 默认行为:大多数原生事件都会冒泡(focus/blur等除外)
代码示例
// 添加冒泡阶段事件监听 (第三个参数为false或省略)
document.querySelector('#outer').addEventListener('click', function() {
console.log('冒泡阶段 - Outer');
});
document.querySelector('#inner').addEventListener('click', function() {
console.log('冒泡阶段 - Inner');
});
四、完整事件流示例
HTML 结构
<div id="grandparent">
Grandparent
<div id="parent">
Parent
<div id="child">Child</div>
</div>
</div>
JavaScript 代码
const elements = ['grandparent', 'parent', 'child'];
// 为所有元素添加捕获和冒泡监听器
elements.forEach(id => {
const el = document.getElementById(id);
// 捕获阶段
el.addEventListener('click', () => {
console.log(`捕获阶段: ${id}`);
}, true);
// 冒泡阶段
el.addEventListener('click', () => {
console.log(`冒泡阶段: ${id}`);
}, false);
});
// 点击child元素后的输出顺序:
// 捕获阶段: grandparent
// 捕获阶段: parent
// 捕获阶段: child (实际是目标阶段)
// 冒泡阶段: child (实际是目标阶段)
// 冒泡阶段: parent
// 冒泡阶段: grandparent
五、事件流控制方法
1. 阻止事件传播
element.addEventListener('click', function(event) {
event.stopPropagation(); // 阻止继续传播
// event.stopImmediatePropagation(); // 阻止所有后续监听器执行
});
2. 阻止默认行为
element.addEventListener('click', function(event) {
event.preventDefault(); // 阻止默认行为(如链接跳转)
});
3. 事件委托 (利用冒泡)
// 只在父元素上设置监听器,处理子元素事件
document.querySelector('#parent').addEventListener('click', function(event) {
if(event.target.id === 'child') {
console.log('通过冒泡处理child点击');
}
});
六、实际应用场景
-
事件委托:利用冒泡减少事件监听器数量
// 动态列表项处理 document.querySelector('#list').addEventListener('click', function(event) { if(event.target.classList.contains('item')) { console.log('点击了项目:', event.target.textContent); } });
-
拦截处理:使用捕获阶段提前处理事件
// 全局点击拦截 document.addEventListener('click', function(event) { if(event.target.tagName === 'A') { console.log('即将跳转到:', event.target.href); } }, true);
-
自定义组件:控制组件内外事件传播
// 阻止组件内部事件冒泡到外部 customComponent.addEventListener('click', function(event) { event.stopPropagation(); // 组件内部处理逻辑 });
七、注意事项
- 目标阶段特殊性:
- 在目标元素上,捕获和冒泡监听器按注册顺序执行
- 不属于真正的捕获或冒泡阶段
- 不冒泡的事件:
- focus/blur
- load/unload
- mouseenter/mouseleave
- 性能考虑:
- 过多的事件监听器会影响性能
目标阶段特殊性: - 在目标元素上,捕获和冒泡监听器按注册顺序执行
- 不属于真正的捕获或冒泡阶段
- 过多的事件监听器会影响性能
- 不冒泡的事件:
- focus/blur
- load/unload
- mouseenter/mouseleave
- 性能考虑:
- 过多的事件监听器会影响性能
- 合理使用事件委托优化