深入理解 JavaScript 箭头函数的 this:为什么 DOM 事件不推荐用箭头函数?
在学习 JavaScript 时,很多人都会被 this 搞得一头雾水。而当箭头函数出现之后,this 的行为又发生了“根本性变化”,让原本就复杂的规则更加扑朔迷离。
这篇文章系统整理两个非常核心的问题:
- 箭头函数里面到底有没有
this? - 为什么 DOM 事件回调函数通常不推荐使用箭头函数?
如果你对 this 的指向、事件绑定、回调函数执行机制还不够清晰,这篇笔记会帮你彻底理顺。
一、先说结论
1)箭头函数有没有 this?
箭头函数:
- 不会创建自己的
this - 只会继承外层作用域的
this this在函数定义时确定- 永远不会被调用方式改变
本质:
箭头函数的 this 是“继承的”,不是“绑定的”
2)DOM 事件回调推荐用箭头函数吗?
通常不推荐,尤其是:
- 你需要使用
this指向触发事件的元素 - 你需要访问 DOM 节点本身
因为:
事件回调使用普通函数 → this = 触发事件的元素
事件回调使用箭头函数 → this = 外层作用域(通常是 window)
这就是为什么很多代码“明明写在按钮事件里”,却拿不到按钮本身。
二、普通函数的 this 是怎么决定的?
普通函数的 this 是运行时决定的,遵循一个非常重要的规则:
谁调用函数,this 就指向谁
看例子:
const obj = {
name: 'andy',
sayHi: function () {
console.log(this)
}
}
obj.sayHi()
执行时:
调用者是 obj
所以 this = obj
这叫:
隐式绑定
三、箭头函数的 this 为什么完全不同?
来看箭头函数版本:
const obj = {
name: 'andy',
sayHi: () => {
console.log(this)
}
}
obj.sayHi()
很多人以为:
this = obj
但实际结果(浏览器中):
this = window
为什么?
根本原因:箭头函数没有自己的 this
箭头函数不会进行 this 绑定,它只会:
向外查找一层作用域的 this
这叫:
词法作用域绑定(lexical this)
也就是:
写在哪个作用域
就继承哪个作用域的 this
而不是:
谁调用决定
举个真实执行过程
const obj = {
sayHi: () => {
console.log(this)
}
}
箭头函数定义位置:
全局作用域
浏览器中:
全局作用域 this = window
所以最终:
箭头函数 this = window
即使这样调用:
obj.sayHi()
也不会改变。
因为箭头函数根本不看调用方式。
四、普通函数 vs 箭头函数(本质对比)
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
| 是否有自己的 this | 有 | 没有 |
| this 什么时候确定 | 运行时 | 定义时 |
| 是否受调用方式影响 | 是 | 否 |
| 能否被 call/apply/bind 修改 | 可以 | 不可以 |
| 是否适合对象方法 | 非常适合 | 不推荐 |
| 是否适合回调函数 | 可以 | 非常适合 |
五、为什么 DOM 事件不推荐箭头函数?
这是开发中最常踩的坑。
先看正确写法:
button.addEventListener('click', function () {
console.log(this)
})
输出:
触发点击的按钮元素
原因:
浏览器会把 this 自动绑定为:
事件触发元素
这是 DOM 事件的默认规则。
再看箭头函数版本:
button.addEventListener('click', () => {
console.log(this)
})
输出:
window
为什么?
因为:
箭头函数的 this 来自外层作用域。
而不是事件系统绑定的。
也就是说:
DOM 想给你绑定 this
箭头函数:不用,我已经有了
所以绑定失败。
六、真实开发中的典型错误
很多人写:
button.addEventListener('click', () => {
this.style.color = 'red'
})
结果报错:
Cannot read property 'style' of undefined
因为:
this = window
window 没有 style。
正确写法:
button.addEventListener('click', function () {
this.style.color = 'red'
})
七、那箭头函数什么时候适合用?
箭头函数最适合:
1)回调函数
比如:
setTimeout(() => {
console.log(this)
}, 1000)
常见于:
- 定时器
- Promise
- 数组方法
- 异步回调
原因:
避免 this 丢失
2)需要继承外层 this
经典场景:
class Person {
constructor() {
this.name = 'Tom'
setTimeout(() => {
console.log(this.name)
}, 1000)
}
}
如果用普通函数:
this 会变成 window
箭头函数可以自动继承类实例。
八、一个超级重要的判断口诀
如果你需要:
动态 this
用普通函数。
如果你需要:
稳定 this(继承外层)
用箭头函数。
九、对象方法为什么不建议箭头函数?
因为对象方法通常需要:
this = 当前对象
但箭头函数做不到。
const user = {
name: 'Lucy',
sayHi: () => {
console.log(this.name)
}
}
输出:
undefined
因为 this 不是 user。