Z
ZHANK
DOM 与事件

事件冒泡与委托

理解捕获/冒泡机制、事件委托优化

事件冒泡与委托

点击一个按钮,不只是触发了这个按钮的事件,还会一层一层向上传递给所有祖先元素——这就是冒泡。理解它,你就能用"事件委托"用一个监听器处理成百上千个元素。

学完本章你将: 理解捕获/冒泡流程、用事件委托减少监听器、自定义事件。


事件传播 —— 点击一个元素发生了什么

当你点击嵌套结构中的内层元素时,事件会经历三个阶段:

                    捕获阶段 ↓(从外到内)
document → html → body → #outer → #inner
                                        ↓ 目标阶段(到达目标)
                    冒泡阶段 ↑(从内到外)
#inner → #outer → body → html → document
javascript
// 默认 addEventListener 在冒泡阶段触发
document.querySelector("#outer").addEventListener("click", () => {
    console.log("outer 冒泡");       // 后执行
});
document.querySelector("#inner").addEventListener("click", () => {
    console.log("inner(目标)");     // 先执行
});
// 点击 inner:先 inner 后 outer

// 捕获阶段(第三个参数 true)
document.querySelector("#outer").addEventListener("click", () => {
    console.log("outer 捕获");       // 先于冒泡执行
}, true);

阻止传播

javascript
document.querySelector("#inner").addEventListener("click", (e) => {
    e.stopPropagation(); // 到此为止,不再向上冒泡
    console.log("只有我执行");
});

// stopImmediatePropagation —— 更"狠"
el.addEventListener("click", (e) => {
    e.stopImmediatePropagation();
    console.log("第一个");
});
el.addEventListener("click", () => {
    console.log("不会执行"); // 被阻止了
});

事件委托 —— 用一个监听器管理一切

传统方式给每个子元素绑定事件——动态添加的元素没事件。事件委托利用冒泡,在父元素上监听,通过 e.target 判断实际点击的是谁

javascript
// ❌ 每个元素一个监听器,新元素没有
document.querySelectorAll(".item").forEach(item => {
    item.addEventListener("click", handler);
});

// ✅ 事件委托:只在父元素上监听
document.querySelector("#list").addEventListener("click", (e) => {
    let item = e.target.closest(".item"); // 找到最近的 .item 祖先
    if (item) {
        console.log("点击:", item.textContent);
    }
});

// 动态添加的元素自动获得"事件处理"!
list.insertAdjacentHTML("beforeend", '<li class="item">新项目</li>');

💡 核心价值: 列表有 1000 行 → 1 个监听器 vs 1000 个。动态增删元素不需要重新绑定。这是前端性能优化的基本功。

let list = document.querySelector("#list"); list.addEventListener("click", (e) => { // 判断点击的是否是目标元素 if (e.target.matches(".item")) { console.log("点击了:", e.target.textContent); } // 或者用 closest 查找祖先 let item = e.target.closest(".item"); if (item && list.contains(item)) { console.log("委托处理:", item.textContent); } });

// 新添加的元素自动获得事件处理能力 let newItem = document.createElement("li"); newItem.className = "item"; newItem.textContent = "新项目"; list.appendChild(newItem);


---

## 事件委托实战

```javascript
// 菜单委托
document.querySelector("#menu").addEventListener("click", (e) => {
    let action = e.target.dataset.action;
    switch (action) {
        case "edit": handleEdit(e.target.closest("tr")); break;
        case "delete": handleDelete(e.target.closest("tr")); break;
        case "view": handleView(e.target.closest("tr")); break;
    }
});

// 表单输入委托
document.querySelector("#form").addEventListener("input", (e) => {
    if (e.target.matches("[data-validate]")) {
        validateField(e.target);
    }
});

自定义事件

javascript
// 创建自定义事件
let customEvent = new CustomEvent("userLogin", {
    detail: { userId: 123, name: "Alice" },
    bubbles: true,
});

// 监听
document.addEventListener("userLogin", (e) => {
    console.log("用户登录:", e.detail.name);
});

// 触发
document.dispatchEvent(customEvent);