事件冒泡和事件捕获
大约 4 分钟
基本概念
事件冒泡(Event Bubbling)和事件捕获(Event Capturing)是DOM事件传播的两个阶段,它们描述了事件在DOM树中传播的顺序。
事件捕获阶段 (Capturing Phase)
事件从window对象开始,沿着DOM树向下传播
依次经过目标元素的祖先元素,直到到达目标元素本身
这是一个从外到内的过程
事件冒泡阶段 (Bubbling Phase)
事件从目标元素开始,沿着DOM树向上传播
依次经过目标元素的祖先元素,直到到达window对象
这是一个从内到外的过程
完整的事件流顺序
捕获阶段(Capturing Phase) - 从window到目标元素
目标阶段(Target Phase) - 事件到达目标元素
冒泡阶段(Bubbling Phase) - 从目标元素回到window
相关信息
捕获阶段 - 祖父元素
捕获阶段 - 父元素
冒泡阶段 - 子元素
冒泡阶段 - 父元素
冒泡阶段 - 祖父元素
实际应用
- 事件委托:利用冒泡机制在父元素上处理子元素的事件
document.getElementById('grandparent').addEventListener('click', (e) => {
if(e.target.id === 'child') {
console.log('通过事件委托处理子元素点击');
}
});- 阻止事件传播
child.addEventListener('click', (e) => {
e.stopPropagation(); // 阻止事件继续传播
console.log('子元素点击,但不会冒泡');
});- 阻止默认行为
child.addEventListener('click', (e) => {
e.preventDefault(); // 阻止默认行为
console.log('阻止了默认行为');
});重要
不是所有事件都会冒泡,如focus、blur等事件不会冒泡
可以使用event.eventPhase属性查看当前事件处于哪个阶段
- 1: CAPTURING_PHASE (捕获阶段)
- 2: AT_TARGET (目标阶段)
- 3: BUBBLING_PHASE (冒泡阶段)
现代浏览器默认使用冒泡模型,但可以通过addEventListener的第三个参数控制
理解事件冒泡和捕获机制对于处理复杂的事件交互和实现高效的事件处理非常重要。
不会冒泡的事件有哪些
焦点相关事件
- focus - 元素获得焦点时触发
- blur - 元素失去焦点时触发
- focusin - 元素即将获得焦点时触发(会冒泡)
- focusout - 元素即将失去焦点时触发(会冒泡)
鼠标相关事件
- mouseenter - 鼠标进入元素时触发
- mouseleave - 鼠标离开元素时触发
对比:mouseover和mouseout是会冒泡的类似事件
其他事件
- load - 资源加载完成时触发
- unload - 文档或子资源卸载时触发
- abort - 资源加载中止时触发
- error - 资源加载失败时触发
- beforeunload - 页面即将卸载前触发
- resize - 窗口或框架大小改变时触发
- scroll - 元素滚动时触发(虽然技术上会冒泡,但通常不会冒泡到window)
为什么这些事件不会冒泡?
- 这些事件通常与特定元素的特定状态相关,冒泡可能导致意外的行为:
- 焦点事件:冒泡可能导致难以追踪焦点实际所在位置
- 鼠标进出事件:避免在复杂嵌套结构中重复触发
- 加载/错误事件:通常只关心特定元素的加载状态
如何处理这些不冒泡的事件?
方法1:直接在目标元素上添加监听器
document.getElementById('myInput').addEventListener('focus', function() {
console.log('输入框获得焦点');
});方法2:使用会冒泡的替代事件
// 使用focusin代替focus(会冒泡)
document.addEventListener('focusin', function(e) {
if(e.target.matches('.form-input')) {
console.log('表单输入框获得焦点');
}
});方法3:在捕获阶段处理
// 在捕获阶段处理focus事件
document.addEventListener('focus', function(e) {
if(e.target.matches('.form-input')) {
console.log('捕获阶段处理focus事件');
}
}, true);实际应用示例
<div class="form-container">
<input type="text" class="form-input" placeholder="输入框1">
<input type="text" class="form-input" placeholder="输入框2">
</div>
<script>
// 错误的方式 - focus不会冒泡,所以不会触发
document.querySelector('.form-container').addEventListener('focus', () => {
console.log('这种方式不会工作');
});
// 正确的方式1 - 使用focusin
document.querySelector('.form-container').addEventListener('focusin', (e) => {
if(e.target.classList.contains('form-input')) {
console.log('输入框获得焦点:', e.target.placeholder);
}
});
// 正确的方式2 - 在捕获阶段处理
document.addEventListener('focus', (e) => {
if(e.target.classList.contains('form-input')) {
console.log('捕获阶段检测到输入框焦点:', e.target.placeholder);
}
}, true);
</script>