先记住这个答案
当用 addEventListener 注册普通函数(function 声明或表达式)时,事件触发后该函数内的 this 会被绑定为 currentTarget,即调用 addEventListener 的那个元素。箭头函数没有自己的 this,它会捕获定义位置上外层作用域的 this 值,因此不会自动指向事件目标。若想获得目标元素,应使用 event.currentTarget 或采用普通函数。
- 普通函数 this 指向监听器注册的元素(currentTarget)
- 箭头函数 this 继承定义处的外层作用域
- 需要当前元素时应使用 event.currentTarget
- 回调提取为函数引用时 this 可能丢失
addEventListener 如何决定 this
当为元素调用 addEventListener(type, listener) 时,浏览器在注册阶段并不改变 listener 函数本身的 this。但当事件命中元素并触发回调时,内部会以该元素作为 this 值来调用 listener(若 listener 是普通函数)。这意味着普通函数内部可以通过 this 访问注册监听的元素本身,而不必再依赖 event.currentTarget。
如果 listener 是箭头函数,因为箭头函数在定义时已经固定了 this 词法值,所以 addEventListener 无法也无法更改其 this。箭头函数的 this 由外层作用域决定,例如在全局环境定义则 this 为 window(非严格模式),在对象方法中定义则 this 为该对象。因此使用箭头函数就丧失了动态 this 根据目标元素绑定的特性。
多按钮列表中的点击处理
假设有一个任务列表,每个按钮点击时需要获取对应的任务 ID 并更新其样式。如果用普通函数:document.querySelectorAll('.task-btn').forEach(btn => { btn.addEventListener('click', function() { this.classList.toggle('done'); }); }); 这里 this 就是被点击的按钮,可以立即操作 classList。同样的逻辑如果改写为箭头函数,this 不会指向按钮,而是继承定义处的外层词法 this,无法直接获取按钮元素。
正确的做法是在箭头函数内使用 event 参数:btn.addEventListener('click', (event) => { event.currentTarget.classList.toggle('done'); }); 因为箭头的 this 不可靠,但 event.currentTarget 永远指向注册监听的元素。对于需要动态作用域的简单回调,普通函数更简洁;箭头函数需要显式依赖事件对象时则更一致。
this 丢失与无法绑定的边界
当普通函数被作为回调传递时,this 可能会丢失。例如将对象方法直接作为监听器:element.addEventListener('click', this.handleClick); 此时回调内的 this 不再指向对象,而是指向元素(即注册监听的元素)。因为 addEventListener 以元素作为 this 调用该函数,破坏了原始绑定。使用 bind 可以修复:element.addEventListener('click', this.handleClick.bind(this))。
箭头函数则完全无法通过 bind 改变 this,因为箭头函数没有自己的 this,但可以借用事件对象的 currentTarget 来得到当前元素。遇到既需要动态目标元素又需要外层变量时,普通函数配合闭包变量,或箭头函数配合 event.currentTarget 都是可行方案,但要权衡代码可读性和性能:每次 bind 会产生新函数,移除监听器时需要保留引用。
容易答错的地方
- 认为箭头函数中的 this 指向事件目标
- 实际箭头函数没有自己的 this,事件处理中它继承外层词法作用域的值,不会自动指向元素。若需获取当前注册元素,建议使用 event.currentTarget(或 event.target 针对触发元素),而不要依赖 this。很多初学者误将 this 与 event 参数混淆。
- 忽视 this 在回调传递中的变化
- 未使用 addEventListener 直接绑定普通函数时,this 取决于调用方式。如果从对象方法中提取函数后传入 addEventListener,该函数会被当作普通函数调用,this 指向元素,丢失对象引用。解决方法是用 bind 手动固定 this,或者改用箭头函数并通过 event 访问所需元素。
面试官还会怎么问?
在事件处理函数中,this 和 event.currentTarget 总是相同吗?
不一定。当监听器由 addEventListener 注册且以普通函数直接调用(未被 bind)时,this 与 event.currentTarget 相同,均指向注册监听的元素;但如果使用箭头函数或通过 bind 改变 this,则 this 与 event.currentTarget 可能不同,此时应依赖 event.currentTarget。
为什么对象方法直接作为事件监听器时 this 会丢失?
因为 addEventListener 内部将函数作为独立的回调,调用时 this 会被设置为事件目标元素(即注册监听的元素),而不是保存方法的对象。this 是调用时决定的,与定义的环境无关。要保留对象引用,需用 bind 或箭头函数包裹调用。
使用 React 合成事件时 this 绑定与原生 DOM 有何不同?
React 合成事件中,事件监听器是挂在根容器上的代理,但在类组件中,通过 JSX 绑定事件处理器时 React 不会自动把 this 绑定到组件实例,需要在构造函数中 bind 或使用箭头函数。原生 DOM 的 this 规则在 React 中不适用,因为 React 内部封装了事件传递。现在函数组件更倾向使用闭包和 hooks 避免 this 问题。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。