Vue开发中为什么需要对箭头函数保持警惕
对 Vue 原理和 JS 基础不够了解的话,有时会遇到看似比较怪异的问题,比如在 Vue 的 watch 或者 methods 中使用箭头函数,被报 this.xxx is undefined 的。遇到的同学可能通过修改箭头函数为 function 匿名函数或者具名函数就解决了,但其中的原理是什么,本文就来介绍一下。
Vue 中的 watch
Vue 中的实例,比如以组件实例为例,在定义时我们往往需要定义一些对象,比如 props、methods、data、watch、computed 等,尤其是 methods、watch 和 computed 之类的对象,其字段组成都是函数,一般我们直接使用具名函数来定义,比如 watch 中:
watch: {
counter(newval) {
// do something
}
}
这里的 counter 就是具名的,还有一些情况,我们会使用 key value 的形式来定义,例如:
watch: {
counter(newval) {
// do something
},
'person.name': function(newval) {
// do something
}
}
在这个代码中,person.name
是通过 key value 的形式定义的,其中 value 是一个 function 匿名函数。
往往问题就出在这里,对于不知道箭头函数和普通 function 函数区别的同学,可能这里为了简单会写成箭头函数的形式,而如果箭头函数里使用 this 访问了当前 Vue 实例,就可能会报 this.xxx is undefined 的错误。
同样的问题也可以扩展到 methods 和 computed 中的定义。
Vue 中对 this 代理
Vue 中除了对数据 data 的代理外,也存在对当前 Vue 实例的一些访问代理,具体表现为在 methods 、watch 和 computed 中使用 this.xxx 可以直接访问 data 、props 和 methods 中的定义,而无需 this.data.xxx 或者 this.methods.xxx。
注意到这点的同学可能对其中的原理有所猜想,比如将 methods、data、watch、props 和 computed 中的数据挂载一份到当前的 Vue 实例中,但这并非是通过编译挂载实现的,因为我们如果枚举 this ,其实是拿不到上述属性中的对象值的。
其实这里面 Vue 依然使用了代理的思维,在 2.x 中为 Object.defineProperty ,在 3.x 中为 Proxy ,但其实现的目的是一样的,就是将对 this.xxx 的访问重定向到 this.data.xxx 或者 this.methods.xxx 上。
function redirectProxy(instance, target) {
Object.keys(target).forEach(k => {
Object.defineProperty(instance, k, {
get() { return target[k] },
set(val) { target[k] = val }
});
});
}
通过调用上面的方法,可以将 instance.xxx 的访问定向到 target.xxx 上,比如:
redirectProxy(this, this.data);
执行后,我们访问 this.counter 的时候时候实际上访问的是 this.data.counter 。
这跟箭头函数有什么关系?
箭头函数
箭头函数最大的特定,就是它没有自己的 this ,因为它本质上是一种匿名函数,在语义上它的作用域就在其代码块内,由于这个特性,箭头函数没有办法作为构造函数使用。
由于箭头函数没有 this ,所以也就无法改变其 this ,也就是通过 bind 、call、apply 去改变一个箭头函数的 this 指向时,最终都不会成功。
而在 Vue 中,由于需要在 this 中访问到 data 和 methods 中的值,就需要对函数的 this 指向最定义,vue 中使用 call 方法来完成这一步,但由于箭头函数的特点,这个指向其实是失败的,this 最终都指向了一个空对象,这也就导致 this.xxx 永远都是 undefined 。
总结
在 Vue 中并不是不能使用箭头函数,而是由于 Vue 中对一些函数初始化的原因,这些函数需要被 call 重新指定 this ,而箭头函数将得不到真正的 this,这是导致错误的根本原因。
真实开发中,必须做到知其然,也要知其所以然。