JS-一文搞懂 DOM 事件流、冒泡与捕获机制

26 阅读2分钟

前言

在前端开发中,理解事件是如何在 DOM 树中传播的,是实现事件委托、处理复杂交互(如嵌套点击)的基础。本文将带你深入浅出地掌握 DOM 事件流的底层逻辑。

一、 什么是 DOM 事件流?

当你在页面上点击一个按钮时,点击事件并不只是发生在按钮上,而是在按钮及其所有父级元素中按特定顺序传播。

DOM 事件流分为三个阶段:

  1. 事件捕获阶段 (Capture Phase) :事件从最顶层的 window 开始,逐级向下传递,直到到达目标元素。
  2. 处于目标阶段 (Target Phase) :事件到达目标元素本身。
  3. 事件冒泡阶段 (Bubbling Phase) :事件从目标元素开始,逐级向上传递,直到 window这是开发中最常用的阶段。

image.png


二、 事件监听: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 来处理子元素的逻辑。

优点:

  1. 减少内存消耗:不需要为成百上千个子元素分别绑定事件。
  2. 动态适应:新增的子元素无需重新绑定事件。
document.getElementById('parentList').addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
        console.log('点击了列表项:', e.target.innerText);
    }
});