3. 事件对象详解:event对象的属性与方法,target与currentTarget的区别
事件对象,说白了就是浏览器在触发事件时,悄悄塞给你的一个「数据包」。你写的事件处理函数能拿到它,全靠这个对象在背后传递信息。
我刚做前端那会儿,总觉得事件对象可有可无。直到有一次排查一个点击穿透的 bug,折腾了两天才发现——原来是我对 event.target 和 event.currentTarget 的理解出了偏差。嗯,从那以后,我再也不敢小看这个对象了。
3.1 事件对象长什么样?
当一个事件被触发,浏览器会创建一个 Event 对象,然后把它作为第一个参数传给事件处理函数。你可以随便给它起个名字,比如 e、ev、event,我个人习惯用 e,因为写起来快。
element.addEventListener('click', function(e) {
console.log(e); // 看看里面都有啥
});
打开控制台,你会看到一个庞然大物。别慌,常用的属性其实就那么几个。
3.2 核心属性一览
我把最常用的属性整理成了表格,方便你对照着看:
| 属性名 | 类型 | 说明 |
|---|---|---|
type |
String | 事件类型,比如 'click'、'keydown' |
target |
Element | 触发事件的元素(事件源) |
currentTarget |
Element | 绑定事件处理函数的元素 |
eventPhase |
Number | 事件当前所处的阶段(1捕获、2目标、3冒泡) |
bubbles |
Boolean | 事件是否会冒泡 |
cancelable |
Boolean | 事件是否可以取消默认行为 |
defaultPrevented |
Boolean | 是否已经调用了 preventDefault() |
timestamp |
Number | 事件发生的时间戳(毫秒) |
timestamp 在性能分析时特别好用。我曾经用它来测量用户从点击到响应之间的延迟,定位了一个动画卡顿的问题。
3.3 核心方法速览
事件对象不光有属性,还自带几个「大招」。这三个方法你几乎每天都会用到:
preventDefault()— 阻止默认行为。比如点击链接不跳转,表单提交不刷新页面。stopPropagation()— 阻止事件继续传播。调用后,事件不会再冒泡或捕获到其他元素。stopImmediatePropagation()— 阻止事件传播,并且阻止当前元素上其他同类型事件处理函数的执行。
// 阻止链接跳转
document.querySelector('a').addEventListener('click', function(e) {
e.preventDefault();
console.log('链接被拦截了,不会跳转');
});
// 阻止事件冒泡
document.querySelector('.child').addEventListener('click', function(e) {
e.stopPropagation();
console.log('事件到此为止,不会冒泡到父元素');
});
stopImmediatePropagation() 比较「狠」。如果同一个元素上绑定了多个 click 处理函数,调用这个方法后,后面的处理函数都不会执行。我曾经在一个复杂的表单组件里误用了它,导致部分校验逻辑失效,排查了半天才找到原因。
3.4 target 与 currentTarget 的区别
这是面试常考题,也是实际开发中最容易踩坑的地方。我直接说结论:
event.target— 事件触发的源头,也就是用户实际点击的那个元素。event.currentTarget— 你绑定事件处理函数的那个元素。
在事件冒泡或捕获的过程中,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 还是按钮(child),但 currentTarget 已经变成了父元素(parent)。
target 是「谁干的」,currentTarget 是「谁在监听」。
3.5 为什么这个区别很重要?
你想想看,如果你在父元素上做事件委托,想通过 e.target 判断用户点击的是不是某个子元素。这时候如果你误用了 e.currentTarget,那永远拿到的都是父元素本身,委托逻辑就全废了。
我曾经在一个无限滚动列表里做事件委托,一开始写成了 e.currentTarget.dataset.index,结果所有点击都返回 undefined。改成 e.target.dataset.index 之后,问题立刻解决。
3.6 事件阶段与 eventPhase
eventPhase 属性告诉你事件当前处于哪个阶段。它有三个可能的值:
| 常量 | 值 | 阶段 |
|---|---|---|
Event.CAPTURING_PHASE |
1 | 捕获阶段(从根节点向目标元素传播) |
Event.AT_TARGET |
2 | 目标阶段(事件到达目标元素) |
Event.BUBBLING_PHASE |
3 | 冒泡阶段(从目标元素向根节点传播) |
默认情况下,addEventListener 的第三个参数为 false,也就是只在冒泡阶段触发。如果你把第三个参数设为 true,处理函数就会在捕获阶段执行。
// 捕获阶段触发
element.addEventListener('click', handler, true);
// 冒泡阶段触发(默认)
element.addEventListener('click', handler, false);
3.7 知识体系图
下面这张图帮你理清事件对象的核心脉络:
3.8 实战中的避坑指南
最后分享几个我踩过的坑,希望能帮你少走弯路:
- 不要依赖
target的类型 — 在事件委托中,target可能是任意子元素。如果你只期望处理特定标签,记得用matches()或closest()做二次判断。 stopPropagation()不是万能的 — 它只阻止事件传播,不阻止其他同类型事件。如果你需要彻底拦截,用stopImmediatePropagation()。- 检查
cancelable— 不是所有事件都能调用preventDefault()。调用前最好检查一下e.cancelable,否则在某些浏览器下会抛出异常。
e.target 和 e.currentTarget 确认一下。这个习惯帮我避免了很多低级错误。