“为什么我在 Vue 里一用箭头函数就报 this 找不到?”这是我做前端这几年被问得最多的问题之一,尤其是在面试和新人 code review 的时候。箭头函数写起来确实香,少敲几个字母,this 还不用管,但放到 Vue 的选项式 API 里,它就成了最容易翻车的隐形炸弹。网上讲箭头函数和 Vue 的文章很多,大多是“这里不要用箭头函数,那里不要用”的结论式罗列,看完了还是不知道为什么,换个场景照样踩坑。我打算用一篇文章把这件事彻底讲透:先搞清楚箭头函数的 this 到底是什么机制,再逐个拆解 Vue 选项式 API 里的禁区,然后告诉你哪些地方箭头函数反而更好用,最后把 Vue 3 组合式 API 里完全不同的使用规则也梳理清楚。这篇文章适合刚入门 Vue 的新手,也适合写过一阵子但一直被 this 折磨的初中级前端,看完能省下大量排查时间。
1. 先搞懂箭头函数的 this 到底绑定在哪
很多人用箭头函数出错,根子上是没想明白一件事:普通函数和箭头函数的 this 绑定规则是完全相反的两套逻辑。这不是 Vue 的问题,是 JavaScript 语言本身的设计,只不过 Vue 的选项式 API 把这个问题放大了。
1.1 普通函数的 this:谁调用就指向谁
普通函数的 this 是在调用时确定的,规则可以简化成一句话:谁调用了这个函数,函数内部的 this 就指向谁。直接调用fn(),this 在非严格模式下是 window(浏览器里),严格模式下是 undefined;通过对象调用obj.fn(),this 指向 obj;通过fn.call(ctx)调用,this 就是我们手动指定的 ctx。
Vue 选项式 API 之所以能让我们在 methods 里用 this 访问 data、computed 和其他方法,就是因为它利用了普通函数的这个特性。Vue 在初始化组件实例的时候,会遍历 methods 对象里的每一个方法,把它们绑定到组件实例 vm 上,绑定方式类似于vm.methodName = bind(method, vm)。也就是说,你写的:
methods: { handleClick() { this.count++ } }实际执行的时候,Vue 是在拿组件实例去调用这个函数,函数内部的 this 自然就指向组件实例了。这里的核心是:普通函数允许调用方通过 bind/call/apply 改变 this,Vue 才能把方法和实例捆绑起来。
1.2 箭头函数的 this:定义时就焊死了
箭头函数的规则完全不同:它没有自己的 this,函数内部用到的 this 是在定义外层作用域时就确定下来的,之后不管你怎么调用,this 都不会变。用一个生活化的类比来说,普通函数的 this 像是一个可以随时改签的航班,箭头函数的 this 则像是出发前 24 小时已经值机锁死了座位,起飞时间到了,谁来了都没用。
const obj = { name: 'vue', normalFn: function() { console.log(this.name) }, arrowFn: () => { console.log(this.name) } } obj.normalFn() // 'vue',this 指向 obj obj.arrowFn() // undefined,this 指向定义时所在的外层作用域(比如 window 或 undefined)不管你是写obj.arrowFn(),还是把箭头函数单独拿出来调,它内部的 this 永远都是定义那一刻外层作用域的 this。箭头函数的 this 本质上是词法作用域的一部分,和变量提升那套规则是同一套体系。
1.3 连带影响的三个隐藏特性:没有 arguments、不能 new、没有 prototype
箭头函数除了 this 不同,还有三个连带特性,这三兄弟也经常会成为 Vue 项目里的隐性问题:
- 没有自己的 arguments 对象:箭头函数里的 arguments 会沿作用域链向上找,如果外层也没有,就直接报错。在 Vue 的 methods 里想通过 arguments 拿到参数列表,用箭头函数就废了。
- 不能作为构造函数:不能用
new调用,因为箭头函数没有 [[Construct]] 内部方法。Vue 组件选项本身不是靠 new 构造组件的,但如果你在某些工厂函数里试图用箭头函数去构造对象,会直接抛 "not a constructor"。 - 没有 prototype 属性:这个特性在 Vue 中影响相对小,但做插件开发或者在 mixin 里给函数做原型扩展时,会莫名发现问题。
记住这三条,再看 Vue 官方文档里“不要在选项属性或回调上使用箭头函数”的警告,就不会觉得突兀了。官方那句话的全貌是:“箭头函数没有 this,this 会作为变量一直向上级词法作用域查找,直到找到为止……这会导致诸如 this.myMethod() 或 this.$emit() 之类的代码无法正常工作。”现在你应该能完全读懂这句话了。
2. Vue 选项式 API 里的坑:这些位置别用箭头函数
搞清楚了箭头函数 this 的机制,接下来我们逐个看 Vue 选项式 API 里的重灾区。这些位置排列组合起来,就是无数个“为什么我的 Vue 页面一刷新就报错”的经典现场。
2.1 data 必须是一个返回对象的普通函数
先看 data。Vue 2 和 Vue 3 的组件定义里,data 都必须是一个函数,返回一个新的对象。很多人知道“要写成函数”,但没意识到这里也有箭头函数的坑:
// 错误示范 export default { data: () => ({ count: 0, name: this.defaultName // 这里的 this 是什么?模块作用域或 undefined }) }这个 this 不可能是组件实例,因为 data 函数被 Vue 调用时,Vue 希望它内部的 this 指向组件实例,这样才能在 data 里通过 this 访问 props、$router、$store 等实例属性。但箭头函数在定义时已经把 this 锁在了组件文件的最外层作用域里,Vue 的绑定机制完全失效。
还有一个更隐蔽的问题:箭头函数不能作为构造函数,而 data 本质上是一个“每次进到组件就 new 一个数据对象”的工厂。虽然用箭头函数返回对象字面量不至于像new那样直接抛错,但它已经违背了 Vue 对 data 的设计意图:单例共享的隐患。如果 data 返回的对象恰好被某种机制复用,或者你在模块顶层定义了一个对象然后在箭头函数里返回它,多个组件实例之间就会共享同一份数据,改一个全变。
// 正确的常规写法 export default { data() { return { count: 0, name: this.$route.query.name || '' } } }这里给一个经验:如果我在做 Vue 2 项目且组件里 data 需要访问实例属性(比如 this.$store 或 this.$route),我习惯把需要的值在 data 返回之前先用普通变量接一下,再放到返回对象里。这不算强制要求,但能减少很多“为什么 data 里拿不到 store”的困惑。
2.2 methods 里的箭头函数:经典翻车现场
methods 是我见过的箭头函数重灾区第一名。原因很好理解:method 看着像“方法”,写着写着就顺手写了箭头函数。症状也很统一——this 变成 undefined,然后页面白屏或控制台红色报错。
// 错误示范 export default { data() { return { count: 0 } }, methods: { increment: () => { this.count++ // TypeError: Cannot read properties of undefined (reading 'count') }, getTotal() { // 反过来,在普通函数内部又嵌套一个箭头函数时,反而没问题 return this.count * 10 } } }报错的原因很直接:increment 是用箭头函数写的,Vue 想要通过bind(method, vm)给它绑定 this,但箭头函数没有自己的 this,bind 根本不起作用。于是 increment 内部的 this 沿词法作用域往组件模块的外层找,找到的往往是 undefined(现代前端构建工具默认开启严格模式,模块顶层的 this 是 undefined)。
这里我强烈建议理解一下“Vue 在编译或者初始化时到底做了什么”。Vue 2 在 initMethods 里会遍历 methods,对每个方法执行vm[key] = bind(methods[key], vm)。Vue 3 也类似,最终用到的都是普通函数的可绑定特性。也就是说,methods 的正确写法只有一种:普通方法。
// 正确示范 export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } }值得多说一句的是:methods 里经常需要互相调用,比如 handleClick 内部调用 fetchList。如果 handleClick 用普通函数,fetchList 也用普通函数,那么:
methods: { async fetchList() { const res = await api.getList() this.list = res.data }, handleClick() { this.fetchList() // this 指向组件实例,没问题 } }2.3 生命周期钩子与 computed:同样踩雷
生命周期钩子也是箭头函数的重灾区。created、mounted、beforeUnmount 这些钩子,Vue 在调用时同样会绑定组件实例作为 this。如果写成箭头函数:
export default { data() { return { list: [] } }, mounted: () => { this.loadData() // this 不是组件实例,这里直接挂 }, methods: { loadData() { // ... } } }这个错误和 methods 是同一个道理,症状却更隐蔽,因为生命周期钩子里如果不直接调用方法,只是 console.log 一个值,可能不会立刻报错,等你发现数据没加载出来,已经绕了好几圈。
computed 和 watch 同样要小心。computed 的 getter 和 setter 内部通常都需要访问组件实例上的数据和方法:
computed: { // 错误 fullName: () => this.firstName + ' ' + this.lastName, // 正确 fullName() { return this.firstName + ' ' + this.lastName }, // 带 setter 的写法 reversedMessage: { get() { return this.message.split('').reverse().join('') }, set(value) { this.message = value } } }watch 的 handler 如果依赖 this,同样不能用箭头函数:
watch: { // 如果 handler 里要访问组件实例,箭头函数就废了 query(newVal) { this.debouncedSearch() }, // 不需要 this 的场景,箭头函数能跑,但为了统一建议还是普通函数 currentPage(newVal, oldVal) { // 纯逻辑,不碰 this } }2.4 选项式 API 各选项 this 绑定对照表
我把这些经验整理成一张速查表,方便遇到问题的时候直接对号入座:
| 选项位置 | 推荐写法 | this 的预期指向 | 写成箭头函数的结果 |
|---|---|---|---|
| data | 普通函数 | 组件实例 | undefined,访问实例属性直接报错 |
| methods | 普通函数 | 组件实例 | undefined,调用实例方法/数据直接报错 |
| computed getter/setter | 普通函数 | 组件实例 | undefined,计算属性结果异常 |
| watch handler | 普通函数 | 组件实例 | undefined,拿不到组件数据和方法 |
| created/mounted 等生命周期 | 普通函数 | 组件实例 | undefined,初始化逻辑静默失效 |
| filters(Vue 2) | 普通函数 | 组件实例 | undefined,过滤器内 this 丢失 |
这张表可以收藏,笔试或者面试前看一遍,基本能覆盖 90% 的“Vue 中箭头函数 this 丢失”问题。
3. 箭头函数在 Vue 里的正确用法:从回调到高阶函数
看到这里别觉得箭头函数在 Vue 里就只能原地去世了。恰恰相反,在正确的场景里,箭头函数是 Vue 开发者最顺手的工具。它的核心价值在于:需要捕获外层 this 的时候,普通函数做不到,箭头函数是唯一解。
3.1 定时器、事件与 Promise 回调中的 this 保存
Vue methods 里的方法我们要求用普通函数,是为了让 this 指向组件实例。但一旦进入异步回调,比如 setTimeout、setInterval、addEventListener、Promise.then,情况就反过来了:普通函数的 this 会丢失,箭头函数反而能稳稳接住。
export default { data() { return { count: 0, timer: null } }, methods: { startCountdown() { // 错误:普通函数回调中的 this 不再指向组件实例 this.timer = setInterval(function() { this.count-- // 这里 this 是 window / undefined,count 减不动 }, 1000) // 正确:箭头函数捕获了定义位置的 this,也就是组件实例 this.timer = setInterval(() => { this.count-- }, 1000) }, async loadData() { // 正确:then 回调用箭头函数,this 保持组件实例 const res = await api.getData().then(response => { this.loading = false return response.data }) } }, beforeUnmount() { clearInterval(this.timer) } }这套思路在事件监听里也适用:
mounted() { window.addEventListener('resize', this.handleResize) }, methods: { handleResize() { this.width = window.innerWidth // 没问题,Vue 绑定的是这个函数本身 } }但如果是在 init 过程中给普通 DOM 添加监听,写成回调函数就要小心。比如:
mounted() { document.getElementById('box').addEventListener('click', function() { this.visible = true // 这里的 this 指向 DOM 元素,不是组件实例 }) document.getElementById('box').addEventListener('click', () => { this.visible = true // 箭头函数捕获外层 this,指向组件实例,正确 }) }我个人的习惯是:凡是在 methods 内部需要往定时器、事件、Promise 等异步回调中传递函数时,一律用箭头函数。这也是为什么我在公司代码规范里会写一条:methods 本身用普通函数,methods 内部的回调用箭头函数。
3.2 data/methods 内部结合箭头函数的高阶姿势
数组的高阶函数,就是 map、filter、reduce、forEach 这些,它们接收的回调在执行时默认会修改 this 的指向(指向 undefined 或全局对象),如果在回调里想访问组件实例属性,箭头函数是唯一省心的方案。
methods: { calculateTotal() { // 错误:普通函数回调用不了外层的 this.cartItems return this.cartItems.reduce(function(sum, item) { return sum + item.price * item.quantity }, 0) // 正确:箭头函数捕获外层 this return this.cartItems.reduce((sum, item) => { return sum + item.price * item.quantity }, 0) }, formatItems() { return this.items.map(item => ({ ...item, label: this.formatLabel(item) // 这里的 this 指向组件实例 })) } }还有一种场景:防抖节流。比如搜索框输入后 300ms 触发接口请求,很多人用 lodash 的 debounce:
methods: { // 错误:debounce 内部回调执行时,this 已经丢失 onSearch: debounce(function() { this.fetchResults() // this 是 undefined }, 300), // 正确:用普通方法 + 箭头函数包装 // ... }这里踩坑要区分两种情况:如果 debounce 包在普通方法内部使用,里面的回调写箭头函数没问题;如果直接把 debounce 包装后的函数放到 methods 上,等触发时 this 就丢了,因为 debounce 内部最终是用自己的定时器调用目标函数,调用场景已经不是组件实例了。遇到这种场景,我的解法是把 debounced 函数创建在 created 里,并用箭头函数保住 this:
created() { this.debouncedSearch = debounce((keyword) => { this.search(keyword) // 这里的 this 指向组件实例 }, 300) }, methods: { search(keyword) { /* ... */ } }3.3 模板事件处理:能用箭头函数吗
模板里的事件处理,@click="handleClick",这里能不能直接写箭头函数表达式?比如<button @click="() => handleClick()">。技术上 Vue 支持这种写法,但我不建议,原因有两条:
- 模板里写内联箭头函数会让模板变乱,调试的时候很难断点。
- 如果箭头函数内部需要 this,范围就很微妙,普通用户很难判断此时的 this 是什么。Vue 模板编译会在渲染上下文中调用函数,这个上下文里 this 指向组件实例代理,但你写一个箭头函数包了一层,里面的 this 就不一定是你想的那个了。
简单说:模板事件处理器,最稳妥的写法还是@click="handleClick",methods 里写普通方法。如果非要传递参数,可以@click="handleClick(item.id)",这也是普通方法表达式。
3.4 Vuex / Pinia / Router 中的边界情况
很多项目是 Vue + Vuex + Vue Router,箭头函数在这里也有边界。
先看 Vuex。Vuex 3 的 mutation/action 里,this 指向 store 实例,所以如果你在 mutation 里写成箭头函数,想通过 this 访问 store 的其他属性就会失败。不过 Vuex 更常见的写法是从参数里解构 commit、state:
// 有 this 依赖,不能用箭头函数 mutations: { setUserInfo: (state, payload) => { // 想通过 this 访问其他 mutation?对不起,this 不是 store }, setUserInfo(state, payload) { state.userInfo = payload } } // 没有 this 依赖,箭头函数能跑,但为了风格一致还是建议普通函数 actions: { fetchUser: ({ commit }) => { return api.getUser().then(res => commit('setUserInfo', res.data)) } }Vue Router 的导航守卫,比如router.beforeEach((to, from, next) => {}),这类回调不涉及组件实例 this,用箭头函数完全没问题。但组件内守卫里有讲究:
// beforeRouteEnter 本来就不让用 this,箭头函数无所谓 beforeRouteEnter: (to, from, next) => { next(vm => { // 这里的 vm 才是组件实例 }) }, // beforeRouteUpdate 在这里是有组件实例 this 的,但箭头函数拿不到 // 所以要用普通函数 beforeRouteUpdate(to, from) { this.loadData(to.params.id) }Pinia 的情况和 Vuex 类似。Options 风格的 actions 里,this 指向 store 实例,建议用普通函数;Setup 风格定义 store 时,因为你完全面向 ref/reactive 编程,箭头函数随便用,因为在 setup 里你根本不需要 this。
4. Vue 3 组合式 API 完全不同:箭头函数可以放心用
Vue 3 推出组合式 API 之后,this 问题被大大稀释了。原因很简单:组合式 API 的设计目标就是让你不再依赖 this,而是通过 ref、reactive、computed 这些显式的变量来组织状态和逻辑。这直接改变了箭头函数的使用规则。
4.1 setup 的 this 本来就是 undefined
先说一个让很多人意外的点:Vue 3 的 setup 函数里,this 不是组件实例,而是 undefined。这是官方设计,setup 的执行时机在组件实例创建之前,this 尚未绑定,所以官方文档明确说“在 setup 中你应该避免使用 this,因为它不会指向组件实例”。
export default { setup() { // 这里的 this 是 undefined console.log(this) // undefined const count = ref(0) const increment = () => { count.value++ // 完全不依赖 this,箭头函数毫无问题 } return { count, increment } } }既然 setup 里本来就没有 this,那在 setup 内部定义方法时用箭头函数,就不会有“this 丢失”的问题。恰恰相反,因为组合式函数往往要把一组逻辑包在一起,箭头函数的简洁写法反而更顺手。
在<script setup>语法糖里也是一样的逻辑:
<script setup> import { ref, computed } from 'vue' const count = ref(0) const double = computed(() => count.value * 2) // 这里写成箭头函数或普通函数都行,推荐箭头函数作为风格 const increment = () => { count.value++ } </script> <template> <button @click="increment">count is {{ count }}</button> </template>这里我建议团队内统一风格:在 script setup 顶层和 setup 函数内部,一律用箭头函数声明普通逻辑函数。因为这里不涉及 this,箭头函数短,还能避免误用 this。
4.2 组合式函数开发里的箭头函数实践
组合式函数(Composables)就是把状态和相关行为抽成一个函数:
// useCounter.js import { ref, computed, onMounted } from 'vue' export function useCounter() { const count = ref(0) const isEven = computed(() => count.value % 2 === 0) function increment() { count.value++ } function decrement() { count.value-- } onMounted(() => { console.log('counter mounted') }) return { count, isEven, increment, decrement } }在这里,increment 和 decrement 写普通函数或箭头函数没有任何区别,因为它们内部用的是闭包变量 ref 的 value,不涉及 this。我写了多个中大型 Vue 3 项目之后,得出的组合式函数内部风格建议是:watchEffect、computed、onMounted 等回调一律箭头函数,导出给模板用的方法保持普通函数或箭头函数都行,团队定一个规则即可。
4.3 watchEffect / watch 回调与箭头函数
watchEffect 和 watch 的默认回调,官方推荐就用箭头函数,因为设计上它们压根不依赖 this:
const count = ref(0) const user = reactive({ name: 'vue' }) watchEffect(() => { console.log(count.value) }) watch(count, (newVal, oldVal) => { console.log(newVal, oldVal) }) watch( () => user.name, (newVal, oldVal) => { // 箭头函数在这里没有任何问题 } )有人会问:那如果 watch 的回调里需要访问组件方法呢?在<script setup>里,组件方法本身就是通过 import 或函数声明定义的变量,直接闭包引用即可,完全不需要 this:
<script setup> import { watch, ref } from 'vue' const query = ref('') function search() { // 发起接口请求 } watch(query, (newVal) => { // 直接调用 search,不需要 this.search search() }) </script>这其实就是组合式 API 最大的收益之一:把所有依赖关系变成显式的词法闭包,你不再需要猜 this 是谁。
4.4 render / JSX 中的写法
Vue 3 里用 render 函数或 JSX 写组件时,箭头函数也很常见。如果你用的是组合式 API + render 函数:
import { h, ref } from 'vue' export default { setup() { const count = ref(0) return () => h('button', { onClick: () => count.value++ }, `count is ${count.value}`) } }render 函数内部的事件回调,写成箭头函数非常自然,因为闭包直接捕获了 setup 作用域里的 count。
如果使用选项式 API 的 render 方法,那就要注意 this 了,因为 render 方法内部 this 是组件实例代理:
export default { data() { return { count: 0 } }, render() { // 这里必须用普通函数,因为需要 this.count return h('button', { onClick: () => this.count++ // 事件回调里可以用箭头函数,因为需要捕获 render 的 this }, `count is ${this.count}`) } }这种混合写法在迁移老项目时经常遇到,我的建议是要分清楚“哪一层需要 this”:这一层需要 this,就用普通方法;这一层只是回调,需要继承外层 this,就用箭头函数。这套逻辑能帮你应对几乎所有 Vue 3 的 this 问题。
5. 实战排查:this 丢失问题定位与修复
前面讲了原理和场景,最后分享一些我实际排查 Vue 项目 this 问题时积累的经验。如果你在代码里看到类似 “Cannot read properties of undefined (reading 'xxx')” 的报错,十有八九就是箭头函数把 this 弄丢了。
5.1 五个高频翻车场景与报错信息
我把这些年见到的真实报错场景整理一下,你可以对照排查:
| 场景 | 典型写法 | 报错/现象 | 修复方案 |
|---|---|---|---|
| methods 里写了箭头函数 | handleClick: () => { this.load() } | Cannot read properties of undefined (reading 'load') | 改为普通方法 |
| data 里访问 this | data: () => ({ name: this.$route.query.name }) | this是 undefined,name 取不到 | data 改为普通函数 |
| 生命周期钩子箭头函数 | created: () => { this.init() } | 初始化逻辑根本不执行,页面数据空白 | created 改普通函数 |
| 定时器回调里用普通函数 | setInterval(function(){ this.count-- }, 1000) | count 不变化,或者报 count 找不到 | 回调改箭头函数 |
| 事件监听回调普通函数 | el.addEventListener('click', function(){ this.open() }) | 一直报 open 不是函数 | 回调改箭头函数,或把 this 存到变量 |
这五个场景是我在带新人和做 code review 时最常看到的,基本覆盖了 80% 的 this 相关问题。
5.2 排查思路:先问 this 从哪来
遇到 this 异常,我一般不会先去翻代码,而是先问三个问题:
- 这个函数是普通函数还是箭头函数?
- 这个函数定义位置的外层是否有 this?外层的 this 是什么?
- 这个函数被调用时,调用方式是什么?
如果是普通函数,this 由调用方式决定,赶紧去看谁调用了它;如果是箭头函数,this 由定义位置决定,赶紧去看它定义在外层的哪个作用域。把思路捋顺了,定位问题一般不会超过五分钟。
还有一个技巧:在可疑代码里临时加一行console.log(this),先确认 this 到底是 undefined、window 还是组件实例。然后根据结果反推是否用了箭头函数。工具层面上,Vue DevTools 里能看到组件实例,但我建议先学会在代码里判断,这是基本功。
5.3 场景速查表:普通函数还是箭头函数
把全文的核心规则浓缩成一张决策表,什么时候用普通函数,什么时候用箭头函数:
| 场景 | 建议 |
|---|---|
| 定义 Vue 选项式 API 的 data/methods/computed/lifecycle/watch handler | 普通函数 |
| Vue 2 filters 里需要访问 this | 普通函数 |
| methods 内部使用 setTimeout/setInterval/Promise/事件监听回调 | 箭头函数 |
| methods 内部使用 map/filter/reduce/forEach 且回调需要访问 this | 箭头函数 |
模板事件处理器@click="xxx" | methods 中普通函数方法 |
| 组合式 API setup / script setup 内定义逻辑函数 | 推荐箭头函数 |
| watchEffect / watch 回调用到的是闭包变量 | 箭头函数 |
| render 函数(选项式) | 外层普通方法,内部回调用箭头函数 |
| Vuex/Pinia 的 actions(options 风格)需要 this | 普通函数 |
| Vuex/Pinia 的 actions 用参数解构(setup 风格) | 箭头函数/普通函数均可,统一即可 |
这张表就是我对 Vue 箭头函数使用规则的全部心得。你把它保存在项目文档里,新同事接手代码时直接发过去,能省很多沟通成本。
5.4 我现在的团队约定
最后分享一个我自己的项目管理小实践。为了避免大家在 this 上反复踩坑,我后来在代码规范里加了几条硬性约定:
- 选项式 API 组件的 data、methods、computed、watch handler、生命周期钩子,一律用普通函数/方法,不用箭头函数。
- methods 内部嵌套的回调,如 setInterval、事件监听、Promise then、数组高阶函数回调,一律用箭头函数。
- 组合式 API(setup / script setup)中,不用 this,所有共享状态通过 ref/reactive + 显式返回,函数定义尽量用箭头函数。
- 代码 review 模板里有一条专门的检查项:搜索“=> {”出现在 options 根级的位置必须说明理由。
这些约定执行了一年多,团队里因为 this 问题提的 bug 基本绝迹了。其实箭头函数本身没有好坏之分,它只是两种 this 规则的其中一种,真正的坑是我们把规则用错了场景。理解了机制,再按照场景去套用,你会发现 Vue 里箭头函数的使用规则一点都不复杂,甚至可以说相当统一:选项式 API 的根级选项要 this,用普通函数;嵌套回调要继承 this,用箭头函数;组合式 API 根本不依赖 this,随手写都行。我的个人体会是,遇到 Vue 的 this 问题,与其死记硬背“这里不能用箭头函数”,不如先停下来问一句:这里的 this 应该是谁?答案是组件实例,就选普通函数;答案是不需要 this 或者需要引用外层 this,箭头函数就是最好的选择。把这个习惯带到日常开发里,你不但能在 Vue 里用好箭头函数,换个框架、换个语言,这套辩证的思考方式也一样用得上。