箭头函数this指向理解修正 - 2020-11-25

0. 起因

var a = 1;
var s = {
 a: 2,
 fn() {
     setTimeout(() => {
       console.log(this.a)
     })
 },
 fn2: () => {
   console.log(this.a)
 }
}
var sf = s.fn;  
sf()   //1, 
var a = 1;
var s = {
 a: 2,
 fn() {
     setTimeout(() => {
       console.log(this.a)
     })
 },
 fn2: () => {
   console.log(this.a)
 }
}
s.fn2()  // 1
s.fn()   // 2

其实就是相当于箭头函数外的this是缓存的该箭头函数上层的普通函数的this。如果没有普通函数,则是全局对象(浏览器中则是window)

1. 箭头函数this指向定义

箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

  • 普通函数的列子:
var name = 'window'; // 其实是window.name = 'window'

var A = {
   name: 'A',
   sayHello: function(){
      console.log(this.name)
   }
}

A.sayHello();// 输出A

var B = {
  name: 'B'
}

A.sayHello.call(B);//输出B

A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window

从上面可以看到,sayHello这个方法是定义在A对象中的,当当我们使用call方法,把其指向B对象,最后输出了B;可以得出,sayHello的this只跟使用时的对象有关。

  • 箭头函数改造后:
var name = 'window'; 

var A = {
   name: 'A',
   sayHello: () => {
      console.log(this.name)
   }
}

A.sayHello();// 还是以为输出A ? 错啦,其实输出的是window

我相信在这里,大部分同学都会出错,以为sayHello是绑定在A上的,但其实它绑定在window上的,那到底是为什么呢?

箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

一开始,我重点标注了“该函数所在的作用域指向的对象”,作用域是指函数内部,这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。

  • 那如何改造成永远绑定A呢:
var name = 'window'; 

var A = {
   name: 'A',
   sayHello: function(){
      var s = () => console.log(this.name)
      return s//返回箭头函数s
   }
}

var sayHello = A.sayHello();
sayHello();// 输出A 

var B = {
   name: 'B'
}

sayHello.call(B); //还是A
sayHello.call(); //还是A

OK,这样就做到了永远指向A对象了,我们再根据“该函数所在的作用域指向的对象”来分析一下:

  1. 该函数所在的作用域:箭头函数s 所在的作用域是sayHello,因为sayHello是一个函数。
  2. 作用域指向的对象:A.sayHello指向的对象是A。

所以箭头函数s 中this就是指向A啦 ~~

2. 继续深入,何时算箭头函数的 “定义”?

箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

  • 把上面的代码改造下:
var name = 'window'; 

var A = {
   name: 'A',
   sayHello: function(){
      var s = () => console.log(this.name)
      return s//返回箭头函数s
   }
}

var sayHelloA = A.sayHello();
sayHello();// 输出A

var B = {
   name: 'B'
}

sayHello.call(B); //还是A
sayHello.call();

var sayHelloB = A.sayHello.call(B);
sayHello2();  //输出B, 变成B了

所以,上面代码里,箭头函数被定义是在sayHelloAsayHelloB变量被声明时,也就是说是在A对象的sayHello方法运行时,箭头函数s才真正的被定义,进而也是在这个时候,箭头函数s的this指向才被确定:

var sayHelloA = A.sayHello();
var sayHelloB = A.sayHello.call(B);

分别被指向了A和B(由于call的存在,箭头函数s在定义时,sayHello的this被指向了对象B)

3. “箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象

“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。

这里先对箭头函数的定义时做下梳理:

function foo(){
  setTimeout(() => {
    console.log("id:", this.id)
  }, 100);
}

foo.call({ id:42 });

请问,上面代码的{ id: 42 },到底是箭头函数定义时所在的对象,还是运行时所在的对象?

你认为,答案是后者。这是不对的。

因为,这个例子中,箭头函数位于foo函数内部。只有foo函数运行后,它才会按照定义生成,所以foo运行时所在的对象,恰好是箭头函数定义时所在的对象

不要把箭头函数所在的函数箭头函数本身混淆了。

引用总结:

我们可以把lambda表达式(即箭头函数)看成一个变量,在代码运行“遇到”(看见)它之前,这个变量是未定义的;遇到的时候,就会把运行时的上下文绑定在展开函数的this上。此时lambda表达式(即箭头函数)内部的操作并没有被执行,它作为一个已经被定义并绑定了this的函数实体存在,等待被实际调用。

这里强调函数定义是一个动态的概念,是外层代码执行时才产生了箭头函数的定义。之所以会觉得理解上有问题,是因为“定义”这个词配合上“外层”的概念,很容易让人理解为:

提到的“定义”是一个代码层面的概念,并且是与代码书写位置有关的,而不是一个“运行时”的概念。

但这是错的,不能以代码书写的位置来作为箭头函数被定义的时期,而应该根据外层代码执行时来确定。

现在再看这个问题,即箭头函数的this是它真正被定义时候的“运行时”上下文,而不是箭头函数实际使用(即内部语句被执行)时的上下文。

  • 写在最后:
function foo() {
  return () => {
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()();
var t2 = f().call({id: 3})();
var t3 = f()().call({id: 4});
function foo() {
  return () => {
    console.log('f:', this.id);  // f: 1
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});
// this -> {id: 1}
// f = () => {
//   console.log('f:', this.id);
//   return () => {
//     return () => {
//       console.log("id:", this.id);
//     };
//   };
// };

var t1 = f.call({id: 2})()();
// this -> {id: 1}
// t1 = () => {
//   console.log('t1:', this.id);   t1: 1
//   return () => {
//     console.log("id:", this.id);
//   };
// }()()

var t2 = f().call({id: 3})();
// this -> {id: 1}
// t2 = () => {
//   console.log('t2:', this.id);   // t2: 1
//   return () => {
//     console.log("id:", this.id);
//   };
// }.call({id: 3})();

var t3 = f()().call({id: 4});
// this -> {id: 1}
// t3 = () => {
//   console.log('t3:', this.id);   // t3: 1
//   return () => {
//     console.log("id:", this.id);
//   };
// }().call({id: 4})

https://github.com/ruanyf/es6tutorial/issues/150

你可能感兴趣的:(箭头函数this指向理解修正 - 2020-11-25)