关于闭包、箭头函数、this

闭包的梗过不去了,来几道题解放来让自己放弃挣扎(还掺杂this、箭头函数等)

1️⃣

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var name = 'global';
var obj = {
  name: 'obj',
  dose: function(){
    this.name = 'dose';
    return function(){
      return this.name;
    }
  }
}

console.log(obj.dose().call(this));

如果你设想的输出与结果不同,请你务必看下去。首先,要明确this的特性,this非常动态,它的指向只与调用包含它的函数的方式有关。当我们调用obj.dose()时,此时的this与obj保持一致,所以this.name = 'dose'将obj的name属性值改成了dose。obj.dose()返回了一个函数,然后我们使用call指定了上下文,指定为了this,指定的这个this是global对象,因为我们当前处于全局上下文!,所以返回的这个函数内的this被显示指定为了global对象,而全局上下文中,确实存在一个name变量,值为global。

2️⃣

1
2
3
4
5
6
7
8
9
let name = 'global';
const obj = {
  name: 'obj',
  dose: () => {
    this.name = 'dose';
    return () => this.name;
  }
}
console.log(obj.dose().call(this));

如果你设想的输出与结果不同,请你务必看下去。这道题我们换个思路,因为我们也发现了,这里面都是箭头函数,箭头函数的this又与ES5的不同。我们首先注意到obj.dose()返回了一个箭头函数,然后在这个箭头函数身上调用了call方法,然而

箭头函数一旦被定义,它的this就不会再被改变

所以返回的箭头函数它就算call王母娘娘都没用,都不会再改变this。所以我们可以简化这道题,这道题问的其实是obj.dose()()输出什么!然而事情总是没那么简单,我们观察到obj.dose它本身竟然也是一个箭头函数,刚才我们说过箭头函数一旦被定义,它的this就不会再被改变,但箭头函数的this还有一个特点,那就是

箭头函数的this与离它最近的包裹它的外层的普通函数(也就是非箭头函数)的this保持一致,如果外层没有普通函数,则与global对象保持一致。

上面还有一个深层意思没表达出来,那就是,箭头函数会忽略它的外层的箭头函数去找普通函数。介绍完箭头函数的特性我们回到题目,obj.dose外层没有普通函数,那么它的this是global,所以this.name = 'dose'这句将global的name属性赋值为dose,然后返回的箭头函数的this也是global,它会忽略obj.dose这个箭头函数,再往外找,发现外面没有函数了,所以指向了global,而我们global的name属性刚刚被赋值成了dose,所以最终输出的结果为dose。

3️⃣

1
2
3
4
5
6
7
8
9
let name = 'global';
const obj = {
  name: 'obj',
  dose: () => {
    name = 'dose';
    return () => name;
  }
}
console.log(obj.dose().call(this));

如果你设想的输出与结果不同,请你务必看下去。首先!还是先把题简化,我们发现在最后依旧是call(this),然而我们用放大镜看也没发现这道题有this这个关键字,所以这道题与this无关,仅仅与闭包有关,简化为obj.dose()()输出什么。我们一定要知道,箭头函数的this与原先的this规则不一样,但,闭包的特性与之前的特性一模一样。所以obj.dose执行时,先把name改为了dose,改的是哪个name呢?其实是全局的name,因为定义这个dose方法时,它的外层没有函数,所以他里面的name则是全局上下文中的name!!!然后返回的箭头函数的name自然也是全局上下文的name,所以输出结果是dose。此题的重点就是理解箭头函数的闭包特性并没有与ES5的普通函数的特性一模一样。

4️⃣

1
2
3
4
5
6
7
8
9
let name = 'global';
const obj = {
  name: 'obj',
  dose: () => {
    this.name = 'dose';
    return (() => this.name).bind(this);
  }
}
console.log(obj.dose().call(this));

如果你设想的输出与结果不同,请你务必看下去。这道题与第二题原理一模一样,唯一的区别在于返回的箭头函数(() => this.name).bind(this)bind了一下this,记得我们说过call无法改变箭头函数的this吗?bind也不能!就算你是在定义箭头函数时bind了this也不好使,!apply也不能!!!所以结果为dose。

其实有一个彩蛋,上面的所有的题中的let name = 'global'其实一点用没有,因为this.name的this指向的是global(也就是window),所以我们是在给window的属性赋值,而在全局上下文中用let声明name,这个name不会成为window的属性(不得不说一句太二了这种设计)。

updatedupdated2020-07-082020-07-08