this、箭头函数与普通函数:前端实战避坑指南
JavaScript 中 this 绑定逻辑及箭头函数与普通函数的区别。核心结论是 this 由调用方式决定,箭头函数继承外层作用域 this。文章通过真实报错场景引入,对比两者在 this、arguments、构造函数能力上的差异。重点分析了后台项目中五大易错场景:UI 组件表格回调、Promise/async 异步回调、对象方法封装、事件监听器移除及数组方法回调。最后给出决策清单,指导开发者根据需求选择合适函数类型以避免常见坑点。

JavaScript 中 this 绑定逻辑及箭头函数与普通函数的区别。核心结论是 this 由调用方式决定,箭头函数继承外层作用域 this。文章通过真实报错场景引入,对比两者在 this、arguments、构造函数能力上的差异。重点分析了后台项目中五大易错场景:UI 组件表格回调、Promise/async 异步回调、对象方法封装、事件监听器移除及数组方法回调。最后给出决策清单,指导开发者根据需求选择合适函数类型以避免常见坑点。

无论你是刚学 JavaScript 的小白,还是已经写了几年代码的前端,只要在写后台管理系统,大概率都踩过 this 和箭头函数的坑。
这篇文章不讲特别玄学的底层原理,只回答三个问题:
先看一段后台管理系统里常见的代码:
// 表格操作列有个「删除」按钮
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() 里的回调改成箭头函数,就不会报错了。后面会详细说明原因。
// 1. 普通函数:自己定义、自己调用
function normalFn() {
console.log("我是普通函数,直接调用就执行");
}
// 主动调用普通函数
normalFn(); // 输出:我是普通函数,直接调用就执行
// 2. 回调函数:作为参数传递,由其他函数调用
function callbackFn() {
console.log("我是回调函数,由 forEach 调用");
}
// forEach 是接收回调的主函数,它会遍历数组时调用回调函数
[1, 2, 3].forEach(callbackFn); // 输出 3 次:我是回调函数,由 forEach 调用
// 更常见的写法:匿名回调函数(简化版)
[1, 2, 3].forEach(function(item) {
console.log("遍历到的元素:", item);
});
// 异步回调(典型场景:定时器)
setTimeout(function() {
console.log("1 秒后执行的回调函数"); // 1 秒后才执行,时机由 setTimeout 决定
}, 1000);
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。
| 对比项 | 普通函数 | 箭头函数 |
|---|---|---|
this | 有属于自己的 this,由调用方式决定 | 没有自己的 this,使用外层作用域的 this |
arguments | 有 | 没有(可用 ...args 替代) |
能否 new | 可以 | 不可以 |
| 能否作为构造函数 | 可以 | 不可以 |
this 的区别(案例说明)普通函数的 this 由调用方式决定,而箭头函数没有自己的 this,会'继承'外层作用域的 this。
// 案例 1:对象方法中的 this
const person = {
name: "张三",
// 普通函数作为对象方法
sayNameNormal: function() {
console.log("普通函数 this:", this.name); // this 指向调用者 person
},
// 箭头函数作为对象方法
sayNameArrow: () => {
console.log("箭头函数 this:", this.name); // this 指向外层(全局 window),没有 name 属性
}
};
person.sayNameNormal(); // 输出:普通函数 this:张三
person.sayNameArrow(); // 输出:箭头函数 this:undefined
// 案例 2:嵌套函数中的 this
const obj = {
num: 10,
fn: function() {
// 普通嵌套函数:this 指向全局(非严格模式)
setTimeout(function() {
console.log("普通嵌套函数 this.num:", this.num); // undefined
}, 0);
// 箭头嵌套函数:this 继承外层 fn 的 this(即 obj)
setTimeout(() => {
console.log("箭头嵌套函数 this.num:", this.num);
}, );
}
};
obj.();
解释:
sayNameNormal 由 person 调用,this 就指向 person;sayNameArrow 没有自己的 this,直接用外层(全局)的 this,而全局 this(window)没有 name 属性,所以是 undefined;this,这也是实际开发中箭头函数最常用的场景(避免手动绑定 this)。arguments 的区别(案例说明)普通函数有 arguments 对象(存储传入的所有参数),箭头函数没有,需用剩余参数 ...args 替代。
// 普通函数:有 arguments
function normalFn() {
console.log("普通函数 arguments:", arguments); // 输出传入的参数集合
console.log("第一个参数:", arguments[0]);
}
// 箭头函数:无 arguments,用 ...args 替代
const arrowFn = (...args) => {
// console.log(arguments); // 直接用会报错:ReferenceError: arguments is not defined
console.log("箭头函数 args:", args); // 数组形式存储参数
console.log("第一个参数:", args[0]);
};
normalFn(10, 20, 30);
arrowFn(10, 20, 30);
解释:
arguments 是类数组对象,只能在普通函数中使用;ES6 的剩余参数 ...args,args 是真正的数组,还能使用 map/filter 等数组方法,比 arguments 更灵活。new / 作为构造函数(案例说明)普通函数可以用 new 调用(作为构造函数),箭头函数不行,强行 new 会报错。
// 普通函数:可作为构造函数
function Person(name) {
this.name = name;
}
const p1 = new Person("李四");
console.log("普通函数构造的实例:", p1.name); // 李四
// 箭头函数:不能作为构造函数
const ArrowPerson = (name) => {
this.name = name;
};
try {
const p2 = new ArrowPerson("王五"); // 强行 new 会报错
} catch (e) {
console.log("箭头函数 new 报错:", e.message); // ArrowPerson is not a constructor
}
小结:
this 指向:普通函数的 this 由调用方式决定,箭头函数继承外层作用域的 this(无自身 this);arguments,箭头函数无 arguments,需用 ...args 剩余参数;new 作为构造函数,箭头函数不行,强行 new 会报错。这三个区别是箭头函数和普通函数最核心的差异,其中 this 指向的区别是实际开发中最常遇到、也最需要注意的点。
// ❌ 错误写法:箭头函数里加 this.,会从 window 找方法,导致 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>
// ⚠️ 能运行但不推荐:箭头函数里不加 this.(Vue 会映射到组件方法),但多一层包装无意义
<el-button @click="(row) => handleEdit(row)">编辑</el-button>
原因:
Vue 会自动将方法的 this 绑定到组件实例上(而非 window)。window/undefined),this.handleEdit 会从 window 查找方法(找不到),且方法内部的 this 也会失效;Vue 会把模板里的 handleEdit 隐式映射到「组件实例的 methods」,并自动绑定组件的 this,是最安全高效的方式。结论: 模板事件绑定优先直接写 方法名 (参数)(最优解);尽量不要用箭头函数包装(避免多余的函数创建 + 踩 this 坑);若因特殊场景非要用箭头函数,需去掉 this.(如 (row) => handleEdit(row)),但这种写法无必要,仅作为兜底参考。
// ❌ 错误:.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 时,用箭头函数。
// ❌ 错误:箭头函数作为对象方法,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 时,用普通函数,不要用箭头函数。
// 场景:监听 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 或普通函数,并把引用存到实例上,保证添加和移除用的是同一个函数。
forEach、map、filter 等)// 在 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 时,两者都可以。
可以按下面几条来选:
(arg) => this.method(arg),避免乱包箭头函数。...args。bind 或保存同一引用,保证添加和移除是同一个函数。this,谁调我,this 就指向谁。this,用的是「定义时所在作用域」的 this。因此,在需要「继承」外层 this 的场景(例如 Promise、setTimeout 回调),用箭头函数;在对象方法、构造函数等需要「自己的」this 的场景,用普通函数。需要「动态 this」用普通函数,需要「固定外层 this」用箭头函数。
this 和箭头函数本身不复杂,容易出错的是「在错误场景选错写法」。后台项目里,最容易踩坑的就是:Promise 回调、对象方法、模板事件、事件监听器这几处。记住「谁在调用」「外层 this 是谁」,选普通函数还是箭头函数就不容易错。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online