美文网首页
箭头函数this指向理解修正 - 2020-11-25

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

作者: 勇敢的小拽马 | 来源:发表于2020-11-25 17:07 被阅读0次

    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
    

    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

          本文链接:https://www.haomeiwen.com/subject/moyniktx.html