Vue中的methods配置项中的箭头函数this指向及相关源码分析
之前在使用Vue时遇到一个问题,我们知道在Vue的methods中定义函数时,要想使用到Vue实例或者组件实例的this时,我们就不能使用箭头函数定义方法,因为箭头函数中的this是在函数定义时生成的,并不是函数调用使用时生成。但是我们如果使用了箭头函数,那么其中的this指向哪里呢?我刚开始也是以为肯定是指向Window的,这仅仅是局限于用script映入vue.js时才会指向Window,但其
之前在使用Vue时遇到一个问题,我们知道在Vue的methods中定义函数时,要想使用到Vue实例或者组件实例的this时,我们就不能使用箭头函数定义方法,因为箭头函数中的this是在函数定义时生成的,并不是函数调用使用时生成。
但是我们如果使用了箭头函数,那么其中的this指向哪里呢?我刚开始也是以为肯定是指向Window的,这仅仅是局限于用script映入vue.js时才会指向Window,但其实在Vue-cli中,如果使用箭头函数定义了methods中的方法,那么this是指向undefined的。这是为什么呢?
其实网上很多人对于这一块的分析是有误的,他们都认为由于是开启了严格模式,且Vue源码中使用了全局函数 initMethods 导致了Vue脚手架的methods中箭头函数指向了undefined。其实不然,箭头函数明确是规定了定义时确认了上下文环境中的this就是它最终的this,后续无论使用bind/apply/call都无法改变其中的this。
目录
·this指向undefined的具体原因
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
methods:{
test:()=>{
console.log(this)
}
}
}).$mount('#app')
这段代码中,如果是使用script引入的vue.js,那么test()箭头函数this是指向Window的,因为箭头函数定义时,this就以经根据它所在的上下文生成了this指向,直接在HTML文件中导入vue.js时,上下文环境是全局窗口Window。
但是,当使用 Vue-cli 创建一个新项目时,vue.js库是作为依赖项安装的,并作为模块导入,这意味着使用导入或需要加载模块时,该模块内部的上下文设置为默认情况下不确定。因此,当使用箭头函数定义方法时,此上下文将被绑定到undefined,即this指向undefined。
例如我们在main.js中的new Vue()方法下边测试一个箭头函数和非箭头函数,并且调用,它们的this都是指向undefined的。
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
function test1(){
console.log("test1 : ",this)
}
test1()
const test2 =()=>{
console.log("test2 : ",this)
}
test2()
上述运行输出为:
接下来我们可以看看Vue中的源码是如何将methods中的方法绑定到vm实例对象或者组件实例对象上的:
·Vue2 methods绑定源码分析
function polyfillBind(fn, ctx) {
function boundFn(a) {
const l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx);
}
boundFn._length = fn.length;
return boundFn;
}
function nativeBind(fn, ctx) {
return fn.bind(ctx);
}
// @ts-expect-error bind cannot be `undefined`
//这里自定义了bind方法,为了更好的兼容浏览器
const bind = Function.prototype.bind ? nativeBind : polyfillBind;
function initMethods(vm, methods) {
const props = vm.$options.props;
for (const key in methods) {
{
if (typeof methods[key] !== 'function') {
warn(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`, vm);
}
if (props && hasOwn(props, key)) {
warn(`Method "${key}" has already been defined as a prop.`, vm);
}
if (key in vm && isReserved(key)) {
warn(`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`);
}
}
//这里是我修改测试,新添加的部分
vm[key]() //在绑定前就有了this,为undefined
//这里是我修改测试,新添加的部分
//这里是最重要的部分,以上都是容错性检查
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}
}
其中的noop是一个无操作的空函数,主要作用就是为一些函数提供默认值,避免传入undefined之类的数据导致代码出错,此出表示如果methods中配置项存在不是一个函数的项,我们为其赋值为一个空函数,防止后续报错。
/**
* Perform no operation.
* Stubbing args to make Flow happy without leaving useless transpiled code
* with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
*/
function noop(a, b, c) { }
initMethods 中最后一行代码的作用就是:将methods中的函数直接动态绑定到了vue实例vm上,也从侧面说明它的方法为什么this指向为vm或者组件实例对象。但是我们定义的箭头函数在它绑定之前,我们看到输出,this就已经指向了undefined,那么我们继续往前看,回到源码最初始的地方:
function Vue(options) {
//添加调试的地方
options.methods.test() //在最开始就调用test this输出仍旧是undefined
//添加调试的地方
if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
所以 initMethods 方法只是将 new Vue(options)/Vue.extend(options) 的配置项中的普通函数绑定到Vue实例或组件实例上去,且让普通函数的this指向Vue实例或组件实例。
更多推荐
所有评论(0)