前言
在前端开发中,理解事件是如何在 DOM 树中传播的,是实现事件委托、处理复杂交互(如嵌套点击)的基础。本文将带你深入浅出地掌握 DOM 事件流的底层逻辑。
一、 什么是 DOM 事件流?
当你在页面上点击一个按钮时,点击事件并不只是发生在按钮上,而是在按钮及其所有父级元素中按特定顺序传播。
DOM 事件流分为三个阶段:
- 事件捕获阶段 (Capture Phase) :事件从最顶层的
window开始,逐级向下传递,直到到达目标元素。 - 处于目标阶段 (Target Phase) :事件到达目标元素本身。
- 事件冒泡阶段 (Bubbling Phase) :事件从目标元素开始,逐级向上传递,直到
window。这是开发中最常用的阶段。
二、 事件监听:addEventListener
现代 DOM 操作主要使用 addEventListener 来绑定事件。
语法: target.addEventListener(type, listener, useCapture)
-
type:事件类型,如'click'或'mousedown'。 -
listener:触发后的回调函数。 -
useCapture(布尔值):控制事件在哪个阶段执行。false(默认值) :在 冒泡阶段 触发处理程序。true:在 捕获阶段 触发处理程序。
三、 阻止事件冒泡:stopPropagation
如果你不希望事件继续向上传递(例如点击子元素时不触发父元素的点击事件),可以使用 e.stopPropagation()。
inner.addEventListener("click", function (e) {
console.log("点击了内部 div");
// 核心代码:切断传播路径
e.stopPropagation();
}, false);
四、 移除事件的“致命陷阱”
很多初学者在调用 removeEventListener 时会发现无效。
1. 错误示范:匿名函数 ❌
即使两个函数的代码一模一样,如果它们是匿名函数(或者是不同的内存引用),remove 就不会生效。
let btn = document.getElementById("myBtn");
// 绑定
btn.addEventListener("click", () => {
console.log("Clicked!");
}, false);
// 尝试移除:无效!因为这个箭头函数是一个全新的对象
btn.removeEventListener("click", () => {
console.log("Clicked!");
}, false);
2. 正确示范:具名函数 ✅
必须确保传入 removeEventListener 的函数引用与添加时完全一致。
let btn = document.getElementById("myBtn");
// 抽离处理函数
let handler = function() {
console.log(this.id);
};
// 绑定
btn.addEventListener("click", handler, false);
// 移除:有效!因为引用的是同一个 handler
btn.removeEventListener("click", handler, false);
五、 面试常考题:事件委托 (Event Delegation)
概念: 利用事件冒泡的原理,将事件监听器绑定在父元素上,通过判断 e.target 来处理子元素的逻辑。
优点:
- 减少内存消耗:不需要为成百上千个子元素分别绑定事件。
- 动态适应:新增的子元素无需重新绑定事件。
document.getElementById('parentList').addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
console.log('点击了列表项:', e.target.innerText);
}
});