4、事件对象详解:event对象属性、target与currentTarget区别、阻止默认行为

事件对象,说白了就是浏览器在触发事件时,悄悄塞给你的那个「情报包」。你写监听函数时那个 e 或 event 参数,里面装满了谁触发了事件、在哪个元素上、鼠标坐标、按键状态等等信息。我刚开始学前端时,总觉得这玩意儿就是个黑盒子,直到有一次排查线上 bug,才真正把它摸透了。

4.1 event 对象的核心属性

先列几个我几乎每天都在用的属性。你不需要全背下来,但下面这张表里的,建议你记牢。

属性名 类型 说明
type String 事件类型,比如 "click"、"keydown"
target Element 触发事件的原始元素
currentTarget Element 绑定监听器的元素
eventPhase Number 事件流阶段:1捕获、2目标、3冒泡
preventDefault() Function 阻止默认行为
stopPropagation() Function 阻止事件继续传播
stopImmediatePropagation() Function 阻止传播 + 阻止同元素其他监听器
isTrusted Boolean 是否由用户真实操作触发
我的小习惯:调试时我经常在监听函数第一行写 console.log(e),然后展开看所有属性。比翻文档快多了。

4.2 target 与 currentTarget 的区别

这是面试高频题,也是实际开发中容易踩坑的地方。我直接给你看代码,比讲概念清楚。

<div id="parent">
  <button id="child">点我</button>
</div>

<script>
  document.getElementById('parent').addEventListener('click', function(e) {
    console.log('target:', e.target.id);          // 输出: child
    console.log('currentTarget:', e.currentTarget.id); // 输出: parent
  });
</script>

为什么会这样?因为事件冒泡了。你点的是按钮,但监听器绑在父元素上。

  • target:永远指向触发事件的源头元素。就是你实际点到的那个。
  • currentTarget:指向当前正在处理事件的元素。也就是你绑定监听器的那个。

我遇到过一个问题:有同事在事件委托里用 e.target === e.currentTarget 做判断,结果发现永远不相等。嗯,因为 target 是子元素,currentTarget 是父元素,当然不相等。正确的做法是用 e.target.matches('.some-selector') 来过滤。

注意:在监听函数内部,this 指向 currentTarget,而不是 target。这是很多新手容易搞混的。

4.3 事件流阶段与 eventPhase

事件流分三个阶段:捕获 → 目标 → 冒泡。你可以通过 e.eventPhase 拿到当前处于哪个阶段。值分别是 1、2、3。

我画了一张图,帮你把这三个阶段和 target、currentTarget 的关系理清楚。

事件流三阶段与 target / currentTarget 关系 document → html → body → parent → child 捕获阶段 (eventPhase=1) 目标阶段 eventPhase=2 冒泡阶段 (eventPhase=3) 关键区别: • target:始终指向红色「目标阶段」的元素(触发源头) • currentTarget:指向当前正在处理事件的元素(绑定监听器的元素) • 在捕获/冒泡阶段,currentTarget 与 target 不同;在目标阶段,两者相同

4.4 阻止默认行为:preventDefault()

有些元素天生自带「默认行为」。比如点击 <a> 会跳转链接,点击 <form> 里的提交按钮会刷新页面,右键会弹出菜单。这些行为有时候不是你想要的。

e.preventDefault() 就是用来干掉这些默认行为的。注意,它不会阻止事件传播,只阻止浏览器执行默认动作。

// 阻止链接跳转
document.querySelector('a').addEventListener('click', function(e) {
  e.preventDefault();
  console.log('链接被拦截,不会跳转');
});

// 阻止表单提交
document.querySelector('form').addEventListener('submit', function(e) {
  e.preventDefault();
  // 这里可以做 AJAX 提交
});
我曾经踩过的坑:有一次做自定义右键菜单,我写了 e.preventDefault() 但没生效。查了半天发现,我监听的是 click 事件,而右键菜单的默认行为是在 contextmenu 事件里触发的。记住:preventDefault() 只对当前事件类型有效。

4.5 阻止事件传播:stopPropagation() 与 stopImmediatePropagation()

这两个方法容易混淆。我直接给你对比。

方法 作用 是否阻止同元素其他监听器
stopPropagation() 阻止事件继续向上(或向下)传播 否
stopImmediatePropagation() 阻止传播 + 阻止当前元素上其他监听器执行 是
// 场景:同一个元素绑了两个监听器
btn.addEventListener('click', function(e) {
  console.log('第一个监听器');
  e.stopImmediatePropagation(); // 第二个不会执行
});

btn.addEventListener('click', function(e) {
  console.log('第二个监听器'); // 这行不会输出
});

我个人建议:除非你非常清楚自己在做什么,否则尽量少用 stopPropagation()。因为它会破坏事件委托机制。我见过一个项目,全局用了大量 stopPropagation(),结果导致下拉菜单、弹窗关闭等功能全部失灵。嗯,排查起来相当痛苦。

4.6 实用技巧:事件对象解构

写监听函数时,我习惯把需要的属性解构出来,代码更干净。

// 不推荐
element.addEventListener('click', function(e) {
  const type = e.type;
  const target = e.target;
  const x = e.clientX;
  const y = e.clientY;
});

// 推荐
element.addEventListener('click', function({ type, target, clientX, clientY }) {
  console.log(type, target, clientX, clientY);
});
小技巧:如果你用 TypeScript,可以给事件对象标注类型。比如 (e: MouseEvent) => {},这样编辑器会给你自动补全属性。

4.7 本章小结

事件对象是 UI 事件驱动的核心情报站。你需要记住:

  • target 是源头,currentTarget 是当前处理者
  • preventDefault() 阻止默认行为,不阻止传播
  • stopPropagation() 阻止传播,但不阻止同元素其他监听器
  • stopImmediatePropagation() 是更狠的版本,连兄弟监听器都干掉

这些概念搞清楚了,后面讲事件委托、自定义事件、手势系统时,你才能游刃有余。