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 知识体系图

下面这张图帮你理清事件对象的核心脉络:

事件对象核心知识体系 Event 对象 target / currentTarget type / eventPhase bubbles / cancelable preventDefault() stopPropagation() stopImmediatePropagation() 事件委托(target) 表单验证(preventDefault) 模态框(stopPropagation) 核心:target 是事件源头,currentTarget 是监听者 两者在事件委托场景下最容易混淆,务必区分

3.8 实战中的避坑指南

最后分享几个我踩过的坑,希望能帮你少走弯路:

  • 不要依赖 target 的类型 — 在事件委托中,target 可能是任意子元素。如果你只期望处理特定标签,记得用 matches() 或 closest() 做二次判断。
  • stopPropagation() 不是万能的 — 它只阻止事件传播,不阻止其他同类型事件。如果你需要彻底拦截,用 stopImmediatePropagation()。
  • 检查 cancelable — 不是所有事件都能调用 preventDefault()。调用前最好检查一下 e.cancelable,否则在某些浏览器下会抛出异常。
我的习惯:每次写事件委托,我都会先打印 e.target 和 e.currentTarget 确认一下。这个习惯帮我避免了很多低级错误。