在监听器中我们常用到e.target:
“
e.target表示触发该事件的 DOM 元素(即用户实际点击的那个元素,而不是绑定监听器的元素——这点在事件冒泡中特别重要)”
这个触及了 JavaScript 事件机制的核心。下面我们从事件流模型出发,深入分析:
一、什么是事件冒泡(Event Bubbling)?
✅ 定义:
当一个元素上的事件被触发时,该事件会从最内层(目标元素)开始,逐级向上传播到其祖先元素,直到 document 或 window。
🌰 示例:
<div id="grandparent">
<div id="parent">
<button id="child">Click me</button>
</div>
</div>
<script>
document.getElementById("grandparent").addEventListener("click", (e) => {
console.log("Grandparent clicked, target:", e.target.id);
});
document.getElementById("parent").addEventListener("click", (e) => {
console.log("Parent clicked, target:", e.target.id);
});
document.getElementById("child").addEventListener("click", (e) => {
console.log("Child clicked, target:", e.target.id);
});
</script>
👉 点击按钮时,控制台输出:
Child clicked, target: child
Parent clicked, target: child ← 注意!target 仍是 child
Grandparent clicked, target: child
✅ 关键点:
e.target始终是最初被点击的元素(#child)this或e.currentTarget才是当前处理函数绑定的元素
🔍
e.currentTarget=== 绑定监听器的那个元素
e.target=== 用户真正点击的那个元素
二、与“冒泡”类似的机制:事件捕获(Event Capturing)
✅ 定义:
事件从最外层(如 window)开始,逐级向下传递到目标元素。这是事件流的第一阶段。
📌 W3C 标准事件流三阶段:
- 捕获阶段(Capturing):从
window→document→ ... → 父元素 → 目标 - 目标阶段(Target):事件到达目标元素
- 冒泡阶段(Bubbling):从目标 → 父元素 → ... →
document→window
默认情况下,
addEventListener监听的是冒泡阶段。
如何启用捕获?
element.addEventListener("click", handler, true); // 第三个参数为 true 表示捕获阶段
捕获 vs 冒泡 对比:
| 特性 | 事件捕获 | 事件冒泡 |
|---|---|---|
| 传播方向 | 外 → 内 | 内 → 外 |
| 默认行为 | ❌ 不默认触发 | ✅ 默认触发 |
| 使用频率 | 较低(特殊场景) | 极高(事件委托依赖它) |
e.target | 仍是目标元素 | 仍是目标元素 |
⚠️ 无论捕获还是冒泡,
e.target始终指向最初触发事件的元素!
三、如何“避免”冒泡或捕获?
你不能完全“禁止”事件流(因为它是浏览器标准),但可以阻止其继续传播。
1. 阻止冒泡(最常用)
e.stopPropagation(); // 阻止事件继续向上冒泡
2. 阻止捕获(同上,在捕获阶段调用即可)
// 在捕获阶段的监听器中
element.addEventListener(
"click",
(e) => {
e.stopPropagation(); // 阻止继续向下捕获
},
true,
);
3. 阻止所有后续处理(包括同级监听器)
e.stopImmediatePropagation(); // 不仅阻止冒泡,还阻止同一元素上其他监听器执行
四、典型应用场景与避坑指南
✅ 场景1:模态框点击外部关闭
modal.addEventListener("click", (e) => {
if (e.target === modal) {
// 只有点击 modal 背景才关闭
closeModal();
}
});
利用
e.target判断是否点在了遮罩层本身,而不是内部内容。
✅ 场景2:事件委托 + 阻止冒泡
<ul id="list">
<li><button class="delete" data-id="1">删除</button></li>
</ul>
list.addEventListener("click", (e) => {
if (e.target.classList.contains("delete")) {
e.stopPropagation(); // 防止触发 li 或 ul 的其他点击逻辑
handleDelete(e.target.dataset.id);
}
});
❌ 常见错误:
- 误以为
this是点击的元素(其实是绑定监听器的元素) - 忘记阻止冒泡,导致父级逻辑被意外触发
五、总结
| 概念 | 说明 |
|---|---|
e.target | 永远是用户实际点击的元素,与事件流阶段无关 |
| 事件冒泡 | 事件从子元素向父元素传播(默认行为) |
| 事件捕获 | 事件从父元素向子元素传播(需显式开启) |
| 如何阻止 | e.stopPropagation() 阻止继续传播 |
| 关键区别 | 传播方向不同,但 e.target 始终不变 |
💡 实际开发中,90% 以上使用冒泡 + 事件委托;捕获仅用于极少数需要“提前拦截”的场景(如全局日志、权限校验等)。