前言
无论你是刚学 JavaScript 的小白,还是已经写了几年代码的前端,只要在写后台管理系统,大概率都踩过 this 和箭头函数的坑。
这篇文章不讲特别玄学的底层原理,只回答三个问题:
- 日常写代码该怎么选?(普通函数 vs 箭头函数)
- 为什么这么选?
- 坑最容易出在哪里?
一、一个真实的报错场景
先看一段后台管理系统里常见的代码:
javascript
// 表格操作列有个「删除」按钮
methods: {
handleDelete(id) {
this.$confirm('确定删除吗?').then(() => {
this.deleteApi(id); // ❌ 报错:Cannot read property 'deleteApi' of undefined
});
}
}
很多人会疑惑:我明明在 methods 里写的,this 怎么会是 undefined?
问题在于:this 不是由「你在哪写的」决定的,而是由「谁在调用这个函数」决定的。 而 $confirm().then() 里的回调,是 Promise 内部在调用,普通函数不会自动带上 Vue 实例的 this。
如果把 .then() 里的回调改成箭头函数,就不会报错了。后面会详细说明原因。
二、基础扫盲:this 到底是谁决定的
核心结论:this 由「调用方式」决定,而不是由「定义位置」决定。
| 调用方式 | this 指向 | 典型场景 |
|---|---|---|
| 作为对象方法调用 | 该对象 | obj.fn() → this 是 obj |
直接调用 fn() |
严格模式:undefined;非严格:window | 孤立的函数调用 |
new 调用 |
新创建的对象 | new Foo() |
call/apply/bind |
传入的第一个参数 | 显式指定 this |
| 作为回调传入 | 谁调就指向谁,通常丢 this | setTimeout(fn)、Promise.then(fn) |
关键点:当函数被当作回调传给别人时,谁调这个函数,this 就由谁决定。 比如 setTimeout(fn) 里,是浏览器在调 fn,所以 this 通常是 window 或 undefined,而不是你组件里的 this。
三、箭头函数 vs 普通函数:本质区别
| 对比项 | 普通函数 | 箭头函数 |
|---|---|---|
| this | 有属于自己的 this,由调用方式决定 | 没有自己的 this,使用外层作用域的 this |
| arguments | 有 | 没有(可用 ...args 替代) |
| 能否 new | 可以 | 不可以 |
| 能否作为构造函数 | 可以 | 不可以 |
一句话区分:
- 普通函数:有「自己的」
this,谁调我,this就指向谁。 - 箭头函数:没有「自己的」
this,用的是「定义时所在作用域」的this。
因此,在需要「继承」外层 this 的场景(例如 Promise、setTimeout 回调),用箭头函数;在对象方法、构造函数等需要「自己的」this 的场景,用普通函数。
四、后台项目里最容易写错的 5 种场景
场景 1:Element UI / Ant Design 表格里的回调
javascript
// ❌ 错误写法:在模板里用箭头函数包装,可能拿不到正确的 this
<el-table-column label="操作">
<template slot-scope="scope">
<el-button @click="() => this.handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
// ✅ 正确写法:直接传方法引用,Vue 会帮你绑定 this
<el-button @click="handleEdit(scope.row)">编辑</el-button>
原因: 模板里的事件绑定,Vue 会自动把组件的 this 绑定到方法上。用箭头函数包装后,this 会在定义时就固定,可能指向 window 或 undefined,反而拿不到组件实例。
结论: 模板事件尽量直接写方法名,或写 (arg) => this.method(arg),不要在模板里随便包箭头函数。
场景 2:Promise / async 里的 this
javascript
// ❌ 错误:.then 里用普通函数,this 丢失
handleSubmit() {
this.validateForm().then(function(res) {
this.submitForm(); // this 是 undefined!
});
}
// ✅ 正确:用箭头函数,继承外层的 this
handleSubmit() {
this.validateForm().then((res) => {
this.submitForm(); // this 正确指向组件实例
});
}
原因: .then() 的回调是 Promise 内部调用的,普通函数不会自动绑定组件 this。用箭头函数可以继承 handleSubmit 所在作用域的 this,即组件实例。
结论: 在 Promise、async/await、setTimeout 等异步回调里,需要访问组件/外层 this 时,用箭头函数。
场景 3:对象方法 / API 封装
javascript
// ❌ 错误:箭头函数作为对象方法,this 指向外层(window)
const api = {
baseUrl: '/api',
getList: () => {
return axios.get(this.baseUrl + '/list'); // this.baseUrl 是 undefined!
}
};
// ✅ 正确:用普通函数
const api = {
baseUrl: '/api',
getList() {
return axios.get(this.baseUrl + '/list');
}
};
原因: 箭头函数没有自己的 this,会去外层找。这里的 getList 定义在对象字面量里,外层是全局,this 就是 window(或 undefined),自然拿不到 baseUrl。
结论: 对象方法、Class 方法需要用到 this 时,用普通函数,不要用箭头函数。
场景 4:事件监听器(addEventListener)
javascript
// 场景:监听 window 滚动,组件销毁时需要移除监听
// ❌ 错误:箭头函数每次都是新引用,无法正确 removeEventListener
mounted() {
window.addEventListener('scroll', () => this.handleScroll());
},
beforeDestroy() {
window.removeEventListener('scroll', () => this.handleScroll()); // 移除失败!引用不同
}
// ✅ 正确:保存同一个函数引用
mounted() {
this.boundHandleScroll = this.handleScroll.bind(this);
window.addEventListener('scroll', this.boundHandleScroll);
},
beforeDestroy() {
window.removeEventListener('scroll', this.boundHandleScroll);
}
原因: removeEventListener 必须传入和 addEventListener 时完全相同的函数引用。每次写 () => this.handleScroll() 都会生成新函数,所以无法正确移除。
结论: 需要手动移除监听时,用 bind 或普通函数,并把引用存到实例上,保证添加和移除用的是同一个函数。
场景 5:数组方法的回调(forEach、map、filter 等)
javascript
// 在 Vue 组件里
methods: {
processList() {
const list = [1, 2, 3];
// ❌ 错误:普通函数作为 forEach 回调,this 会丢
list.forEach(function(item) {
this.doSomething(item); // this 是 undefined
});
// ✅ 正确:箭头函数继承外层的 this
list.forEach((item) => {
this.doSomething(item);
});
}
}
原因: forEach 等方法的回调是由数组方法内部调用的,普通函数不会绑定组件 this。用箭头函数可以继承 processList 的 this。
结论: 在 forEach、map、filter、reduce 等回调里需要访问外层 this 时,用箭头函数;不需要 this 时,两者都可以。
五、决策清单:什么时候用谁
可以按下面几条来选:
- 对象方法、Class 方法、构造函数 → 用普通函数。
- Promise、setTimeout、数组方法等回调里要访问外层 this → 用箭头函数。
- Vue 模板事件 → 直接写方法名,或
(arg) => this.method(arg),避免乱包箭头函数。 - 需要 arguments → 用普通函数,或箭头函数 +
...args。 - addEventListener / removeEventListener → 用
bind或保存同一引用,保证添加和移除是同一个函数。
六、一句话口诀
- 普通函数 :有自己的
this,谁调我,this就指向谁。 - 箭头函数 :没有自己的
this,用的是「定义时所在外层」的this。
需要「动态 this」用普通函数,需要「固定外层 this」用箭头函数。
总结
this 和箭头函数本身不复杂,容易出错的是「在错误场景选错写法」。后台项目里,最容易踩坑的就是:Promise 回调、对象方法、模板事件、事件监听器这几处。记住「谁在调用」「外层 this 是谁」,选普通函数还是箭头函数就不容易错。
以上就是本次的学习分享,欢迎大家在评论区讨论指正,与大家共勉。
我是 Eugene,你的电子学友。
如果文章对你有帮助,别忘了点赞、收藏、加关注,你的认可是我持续输出的最大动力~