之前在使用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的具体原因

·Vue2 methods绑定源码分析


·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实例或组件实例。

Logo

前往低代码交流专区

更多推荐