Vue实例中的this指向问题

5,884 阅读5分钟

最近在学习js中的函数,又碰见了this的问题。

查看了很多文章,之前也有总结过,对于正常函数,谁调用的它this就指向谁,而箭头函数没有this,它的this指向就要在函数一开始定义的时候就确定好了,与谁调用它没关系。

但是在Vue实例中,methods中如果用的是正常函数,那么它的this就指向Vue实例;如果是箭头函数,this就指向window对象。

在Vue的官方文档是这么解释的:

methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this自动绑定为 Vue 实例。

注意,不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined。

下面先来看一个例子:

function fun(obj) {
    obj.get();
}

fun({
    get: () => {
        console.log("get");
        console.log(this);
    }
})

猜测一些会打印什么结果呢?

之前有一篇文章介绍了,在寻找箭头函数得this指向时,就看箭头函数外层是否有函数: 如果有,那么外层函数的this就是内部箭头函数的this; 如果没有,那么this就是window。

根据js的作用域,调用fun函数的时候传入了一个对象,对象的大括号并不会构成一个作用域,因此get函数里面的this就会继续往外找,有一个疑惑的地方就是:对象作为参数传入函数,这个函数到底算不算get函数的父级作用域呢?

答案是否定的。

现在我们来打印上面例子的结果发现,this指向window对象,这样就能解释为什么在Vue实例里面传入options时,methods里面箭头函数的this指向window,而不是Vue实例。

其实,我们在new Vue实例时,传入的对象,其作用域不在函数体内,而是在全局作用域中,这个对象一个匿名对象,用法与在函数外层定义一个对象变量,然后将这个变量传进来一样,这个变量就是定义在全局作用域中的。

下面看一个例子可以更清楚的解释上面的情况:

function fun(fn) {
    fn()
}


fun(() => {
    // window
    console.log(this);
})

这里进一步印证了,函数参数里面的箭头函数的this是指向window对象而不是当前函数的。


下面看几个例题进一步研究一下作用域的问题:

例一:

let a = 1;
function fun(){
 a = 2;
}
fun();
console.log(a);

在看上面的例子之前,需要知道的一点是,只有出现{}才算一个作用域,对象和window除外。 上面这个例子有两个作用域,一个是fun形成的函数作用域,另一个是全局作用域。用let定义变量a时,a所处的作用域是全局作用域,然后调用fun函数,在函数里面执行a=2,根据作用域链,这个时候会首先在函数作用域找,没有定义a,然后在全局作用域中找,发现定义了a,此时将a重新赋值为2,因此最后打印a为2。

例二:

let a = 1;
function fun(a){
 a = 2;
}
fun(a);
console.log(a);

上面的例子中,fun函数传入了一个参数a,相当于在函数作用域里面声明了一个变量,即let a;然后执行a=2这句代码时,将函数作用域中的a赋值为2。执行最后一句代码时,在全局作用域中找到a=1,因此最后打印a为1。

例三:

let a = {
    name: 'aa'
}

function fun1(a) {
    a.name = 'bb';
}

fun1(a);
console.log(a);

在上面的代码中,在fun函数里声明了一个变量a,调用fun函数时,传入了一个对象,传对象和传基本类型的值不同,这里传对象,传的是对象在内存中的地址,因此,在fun的函数作用域里面,这个a和函数外面的a都指向内存中的同一个地方,因此,执行a.name,会影响到外面a的值。最后打印a的值为{name:'bb'}。

例四:

let a = {
    name: 'aa'
}

function fun1(a) {
    a = []
}

fun1(a);
console.log(a);

上面的代码中,直接在函数里面把a赋值为一个新的数组,这个操作不会影响到函数外面的a,因为调用fun函数时,传入的是外部的对象所在的内存地址,进入函数体之前,函数作用域中的a和外部a的指向一样,但是在执行完函数体里面的代码后,函数体里面的a就指向了另一块内存地址,外部a的指向没有改变,因此最后打印的结果为{name:'aa'}

最后,再补充一个知识点,我们常说的全局作用域和window对象是不能划等号的。比如用var在全局作用域声明一个变量是无法delete的,因为它的configurabal属性值为false,但是如果属性直接定义在window对象上,就可以使用delete。红宝书里面说,用var在全局作用域中定义的变量、函数,会被当成window对象的属性和方法。如果在函数里面把var操作符删了,直接给变量赋值,如果在函数外部没有这个变量,这个变量将会被挂载到window对象上,如果有这个变量,会重新给这个变量赋值。