美文网首页JS基础知识
JS中函数内部this的探究

JS中函数内部this的探究

作者: 安静的牛蛙 | 来源:发表于2018-04-25 22:41 被阅读0次

    本文参考链接 彻底理解js中this的指向知乎回答 以及 JavaScript中的对象查找

    下面所说的全部为标准模式下的情况,至于严格模式下的this,请参阅另一篇文章最后一部分内容

    首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。

    既然this最终指向调用它的对象,那么我就首先看下,JS中函数的调用分那几种情况。

    JS中函数调用共分为四种类型

    一:Function Invocation Pattern

    诸如foo()的调用形式被称为Function Invocation Pattern,是函数最直接的使用形式,注意这里的foo是作为单独的变量出现,而不是属性,前面并无调用对象。
    在这种模式下,foo函数体中的this永远为Global对象,在浏览器中就是window对象。比如下面的代码:

    function foo(){
        var user = "追梦子";
        console.log(this.user); //undefined
        console.log(this); //Window
    }
    foo();
    

    即使在函数内部调用,由于前面并无调用对象,this还是指向window全局对象,如以下代码

    function printThis() {
      console.log(this)
      let print = function () { console.log(this) };
      print();  
    }
    printThis()  // 第一行打印 window,第二行打印window
    printThis.call([1]);  // 第一行打印[1],第二行打印window
    printThis.call([2]);  // 第一行打印[2],第二行打印window
    

    在上面的代码中,第一条将打印printThis的this,当通过call函数的时候,参数作为this传递到函数中,因此分别打印[1]和[2]。第二条因为没有对象调用print,因此一直打印window。
    即使将print改为匿名函数,比如下面的代码,结果依然没有变化。因此此时匿名函数依然由window调用

    function printThis() {
      console.log(this)
      (function () {
        console.log(this)
      })(); // 匿名函数和上面的print都由window调用
    }
    printThis()  // 第一行打印 window,第二行打印window
    printThis.call([1]);  // 第一行打印[1],第二行打印window
    printThis.call([2]);  // 第一行打印[2],第二行打印window
    

    但是在ES6中,随着箭头函数的引入,这种情况有所变化,将上面的函数改为下面的格式:

    function printThis() {
      console.log(this)
      let print = () => console.log(this);
      print();
    }
    printThis()  // 第一行打印 window,第二行打印window
    printThis.call([1]);  // 第一行打印[1],第二行打印[1]
    printThis.call([2]);  // 第一行打印[2],第二行打印[2]
    

    此时的结果和上面就有所不同,这是因为在箭头函数中,抛弃了自己的this属性,而是直接使用封闭执行上下文的this值。所谓的封闭执行上下文,就是箭头函数出现的地方的代码域。此时第一条和第二条打印内容相同。
    在箭头函数中,所有的this原则,无论是标准模式还是严格模式下,都不再生效。

    二:Method Invocation Pattern

    诸如foo.bar()的调用形式被称为Method Invocation Pattern,注意其特点是被调用的函数作为一个对象的属性出现,必然会有“.”或者“[]”这样的关键符号。
    在这种模式下,bar函数体中的this永远为“.”或“[”前的那个对象,如上例中就一定是foo对象。比如下面代码:

    var o = {
      a:10,
      b:{
        a:12,
        fn:function(){
          console.log(this.a); //12
        }
      }
    }
    o.b.fn(); 
    

    虽然首先调用的对象为o,但最终的函数fn是对象b的一个属性,因此this.a为12。
    然后我们再看下下面的代码

    var o = {
      a:10,
      b:{
        a:12,
        fn:function(){
          console.log(this.a); //12
        }
      }
    }
    var j = o.b.fn;  // 只是定义,并未调用执行,真正的调用执行在下面的j()
    j(); 
    

    此时的this.a应该是多少呢?此时的this其实为window,this.a为undefined。为什么如此呢?回顾一下,在文章的最开始,我们就提到了this的最终指向的是那个调用它的对象。而我们在使用o.b.fn的时候,并没有调用执行,真正的调用执行是在下面的j(),此时的情况和第一种方式一样。

    三:Constructor Pattern

    new foo()这种形式的调用被称为Constructor Pattern,其关键字new就很能说明问题,非常容易识别。
    在这种模式下,foo函数内部的this永远是new foo()返回的对象。比如下面的代码:

    function Foo () {
      this.x = 1;
    }
    Foo.prototype.print = function () {
      console.log(this.x);
    }
    
    let foo = new Foo();
    foo.print(); 
    foo.print.call({x: 2});
    

    此时,第一条打印为1,表示this为foo实例对象。this.x为constructor函数中赋值的1。第二条打印为2,因此为使用了{x: 2}作为对象替换了foo实例对象中的this
    在这里需要注意Function创建对象的一种特殊情况

    function Foo () {
      this.x = 1;
      return {x: 2};
    }
    Foo.prototype.print = function () {
      console.log(this.x);
    }
    let foo = new Foo();
    console.log(foo.x);  // 2
    foo.print();  // funciton print undefined
    

    如果构建函数Foo返回的是一个对象,那么foo就会被这个对象所替换掉,此时foo为{x: 2},其中只有一个x属性等于2,并无print这个方法属性。但是如果我们修改下代码,改成下面的格式:

    function Foo () {
      this.x = 1;
      return 1;
    }
    Foo.prototype.print = function () {
      console.log(this.x);
    }
    let foo = new Foo();
    console.log(foo.x);  // 1
    foo.print();  // 1
    

    因为构建函数Foo返回的是并不是一个对象,那么foo就不会被替换掉,依然是Foo的一个实例对象,此时的foo.xthis.x全部为1。

    四:Apply Pattern

    foo.call(thisObject)foo.apply(thisObject)的形式被称为Apply Pattern,使用了内置的callapply函数。
    在这种模式下,callapply的第一个参数就是foo函数体内的this,如果thisObject是nullundefined,那么会变成window对象。具体代码,我们在上面的三种模式中已经顺带阐述,因此不再赘述。


    练习部分

    查看下下面的代码,分析下打印的结果,然后实际运行下,看下是否和结果一致

    var x = 0;
    function Foo () {
      this.x = 1;
    }
    Foo.prototype.print = function () {
      console.log(this);
      console.log(this.x);
      (function () {
        console.log(this);
        console.log(this.x)
      })()
    }
    
    let foo = new Foo();
    foo.print.call({x: 2});
    

    查看代码,首先注意到Foo对象的print中,第三条和第四条打印是在一个匿名函数中,此时该匿名函数的调用者为window全局变量,因此第四条打印中的this.xwindow.x = 0。再往下看,发现使用{x:2}代替了print函数中的this,因此第一条打印为{x: 2},第二条打印为2。

    如果修改下代码,将print的定义改为箭头函数呢?结果又如何?代码如下:

    var x = 0;
    function Foo () {
      this.x = 1;
    }
    Foo.prototype.print = () => {
      console.log(this);
      console.log(this.x);
      (function () {
        console.log(this);
        console.log(this.x)
      })()
    }
    
    let foo = new Foo();
    foo.print.call({x: 2});
    

    首先,我们知道箭头函数不包含this,它的this为执行上下文中的this。而且箭头函数的执行上下文的判定,就在其定义的时刻决定,我们发现,它是在脚本中定义的,此时的作用域为全局,因此此时,前两条打印和后两条打印一样,都为0和window。

    相关文章

      网友评论

        本文标题:JS中函数内部this的探究

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