最近在学习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对象上,如果有这个变量,会重新给这个变量赋值。