关于this的个人理解

424 阅读5分钟

this关键字是javascript中看起来最复杂的机制,也是最让人困惑的机制,因为当我们初学者在接触 this时,往往习惯性的采用经验总结的方法,这固然是个好办法,但是这个大多数人都惯用的思维方式,在面对this时却让人捉摸不透,好不容易总结了规律,在其它地方却感觉this机制又并不如自己所想的那样,就像是魔法一样,让人不得不思考this内部实现到底是个什么样的机制,在一些学习技巧失效的情况下,正确的清晰认知其实现机制往往是最切实可行的方法。

 当然,实际上,JavaScript 中 this 的机制并没有那么先进,但是开发者往往会把理解过程复杂化,
 毫无疑问,在缺乏清晰认识的情况下,this 对你来说完全就是一种魔法。

本文大部分理解均来自于《你不知道的javascript》一书


知识网络图

1.为什么要用this

- 显式传入上下文对象
  ```
    function identify() {
    return this.name.toUpperCase(); }
    function speak() {
    var greeting = "Hello, I'm " + identify.call( this ); console.log( greeting ); }
    var me = { name: "Kyle" };
    var you = { name: "Reader" };
    identify.call( me ); // KYLE 
    identify.call( you ); // READER 
    speak.call( me ); // Hello, 我是 KYLE 
    speak.call( you ); // Hello, 我是 READER
  ```
  
如果不了解this,这段代码解读起来有些费劲,请暂时抛开这些问题,且听我往下解释。
这段代码可以在不同的上下文对象(me和you),中重复使用函数speak()和identify(),不用针对每个对象写一个函数。
然而,如果不使用this,就需要给函数显示的传入一个对象,可想而知,函数要执行并输出结果,必然需要一个对象
提供依据。(上面的代码中call()已经对this进行了操作)。
  ```
    function identify(context) {
    return context.name.toUpperCase(); }
    function speak(context) {
    var greeting = "Hello, I'm " + identify( context );
    console.log( greeting ); }
    identify( you ); // READER 
    speak( me ); //hello, 我是 KYLE
  ```
  比对上下串代码,可以发现,使用this显得更加优雅,this提供了更加强大的方法来实现隐式传
  输,因此可以将API设计地更加简洁而易于复用。
  
  随着使用的模式越来越复杂,显示传递上下文会让代码变得越来越混乱,使用this则不会这样。

2.对this的常见误解

①.指向自身

    人们很容易把函数理解为指向自身,当然这个推断从this的英语语法上是说得通的,但实际上并不是这样。
    我们来看看代码。
    function foo(num) { 
      console.log( "foo: " + num ); // 记录 foo 被调用的次数
      this.count++; 
    }
      foo.count = 0;
      var i;
      for (i=0; i<10; i++) {
         if (i > 5) { 
           foo( i );
         }
     }
         // foo: 6 
         // foo: 7 
         // foo: 8 
         // foo: 9 
         // foo 被调用了多少次? 
         console.log( foo.count ); // 0 -- WTF?
         
         
     显然结果输出了四次,但是当我们调用count的值时,count仍然时0,说明this并不是指向自身。负责的开发者一
     定会问“如果我增加的count属性和预期的不一样,那我增加的是哪个count ?”实际上,如果他深入探索的话,就
     会发现这段代码在无意中创建了一个全局变量count,它的值为NaN。当然如果他发现了这个奇怪的结果,那一定会
     接着问:“为什么它是全局的,为什么它的值是 NaN 而不是其他更合适的值?” 这个问题留给后面再深入思考。
     
     接下来回到正题,我们怎样才可以让this就是我们想要的对象呢??
     我们对原来的代码稍加修改。
      function foo(num) { 
      console.log( "foo: " + num ); // 记录 foo 被调用的次数
      this.count++; 
    }
      foo.count = 0;
      var i;
      for (i=0; i<10; i++) {
         if (i > 5) { 
           foo( i ).call(foo,i);
         }
     }
         // foo: 6 
         // foo: 7 
         // foo: 8 
         // foo: 9 
         // foo 被调用了多少次? 
         console.log( foo.count ); 
         
         
     我们利用call将this强制绑定在了foo上,这样this.count与foo.count就联系起来了。

②.指向它的作用域

 第二种理解是this指向函数的作用域,在某些情况是正确的,但是在其它一些情况却不然。
 
 需要明确的是,this 在任何情况下都不指向函数的词法作用域。在 JavaScript 内部,作用 
 域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript 
 代码访问,它存在于 JavaScript 引擎内部。
 
 思考一下下面的代码,它试图(但是没有成功)跨越边界,使用 this 来隐式引用函数的词 法作用域:
 ```
  function foo() {
    var a = 2;
    this.bar(); 
      
  }
  function bar() { 
    console.log( this.a );
     }
  foo(); // ReferenceError: a is not defined 
 ```
 
 这段代码存在诸多的问题,最显眼的就是试图用this.bar()引用bar函数,这显然是不可行的,详
 细的内部原理我们往后再细想,最主要的是在了解完词法作用域之后,我们知道,要在foo函数里
 引用外部函数,直接使用词法引用标识符bar(),就可以了。
 他还试图用this联通foo和bar的词法作用域,从而使bar能访问foo里的a,事实告诉我们是不可能
 的,当下一节了解了this到底是什么的时候,相信你会明白为什么。

3.this到底是什么?

 排除了一些错误理解之后,我们来看看 this 到底是一种什么样的机制。 之前我们说过 this 
 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。this 
 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。 
 当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包 
 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 
 其中一个属性,会在函数执行的过程中用到。
 
 也就是说,我们得学习如何寻找函数的调用位置,才能判断函数在执行过程中会如何绑定 this。

4.小结

 this是非常重要的,一直重复的持续困惑并不能帮助我们理解它。
 this既不指向它本身,也不指向作用域。
 this是在调用时绑定的,this取决于调用的方式以及位置。