关于this

作者: 幻城之雪 | 来源:发表于2023-05-17 20:17 被阅读0次

    下定义:this是在执行上下文创建时确定的一个在执行过程中不可更改的变量。

    执行上下文:
    就是JavaScript引擎在执行一段代码之前将代码内部会用到的一些变量、函数、this提前声明然后保存在变量对象中的过程。这个'代码片段'包括:全局代码(script标签内部的代码)、函数内部代码、eval内部代码。而我们所熟知的作用域链也会保存在这里,以一个类数组的形式存储在对应函数的[[Scopes]]属性中。

    this只在函数调用阶段确定,也就是执行上下文创建的阶段进行赋值,保存在变量对象中。

    var a = 1; 
    function fun() { 
    'use strict'; 
    var a = 2; 
    return this.a; 
    } fun();//报错 Cannot read property 'a' of undefined
    

    严格模式下,this指向undefined;

    var a = 1; 
    function fun() { 
     var a = 2; return this.a; 
    } fun();//1
    

    非严格模式下this指向window;

    结论:当函数独立调用的时候,在严格模式下它的this指向undefined,在非严格模式下,当this指向undefined的时候,自动指向全局对象(浏览器中就是window)

    全局环境下,this就是指向自己

    this.a = 1; 
    var b = 1; 
    c = 1; 
    console.log(this === window)//true 
    
    

    //这三种都能得到想要的结果,全局上下文的变量对象中存在这三个变量

    当this不在函数中用的时候会怎样

    var a = 1000; 
    var obj = { a: 1, b: this.a + 1 } 
    function fun() { 
      var obj = { 
       a: 1, 
       c: this.a + 2 //严格模式下这块报错 Cannot read property 'a' of undefined 
    } 
    return obj.c; 
    } 
    console.log(fun());//1002 
    console.log(obj.b);//1001
    

    这种情况下this还是指向了window。那么我们可以单独下个结论:
    当obj在全局声明的时候,obj内部属性中的this指向全局对象,当obj在一个函数中声明的时候,严格模式下this会指向undefined,非严格模式自动转为指向全局对象。

    函数的调用方法:
    1)在全局环境或是普通函数中直接调用

    var a = 1; 
    var obj = { 
     a: 2, 
     b: function () { 
       function fun() { return this.a } 
       console.log(fun()); 
    } } 
    obj.b();//1
    
    

    fun函数虽然在obj.b方法中定义,但它还是一个普通函数,直接调用在非严格模式下指向undefined,又自动指向了全局对象,正如预料,严格模式会报错undefined.a不成立,a未定义。

    当函数独立调用的时候,在严格模式下它的this指向undefined,在非严格模式下,当this指向undefined的时候,自动指向全局对象(浏览器中就是window)。

    2)作为对象的方法

    var a = 1; 
    var obj = { 
      a: 2, 
      b: function() { 
       return this.a; 
      } 
    } console.log(obj.b())//2
    
    

    b所引用的匿名函数作为obj的一个方法调用,这时候this指向调用它的对象。
    这里也就是obj。那么如果b方法不作为对象方法调用呢?啥意思呢,就是这样:

    var a = 1; 
    var obj = { 
      a: 2, 
      b: function() { 
       return this.a; 
      } 
    } 
    var t = obj.b; 
    console.log(t());//1
    
    

    如上,t函数执行结果竟然是全局变量1,为啥呢?这就涉及Javascript的内存空间了,就是说,obj对象的b属性存储的是对该匿名函数的一个引用,可以理解为一个指针。当赋值给t的时候,并没有单独开辟内存空间存储新的函数,而是让t存储了一个指针,该指针指向这个函数。相当于执行了这么一段伪代码:

    var a = 1; 
    function fun() {
      //此函数存储在堆中 
      return this.a; 
    } 
    var obj = { 
      a: 2, 
      b: fun //b指向fun函数 
    } 
    var t = fun;//变量t指向fun函数 
    console.log(t());//1
    
    

    此时的t就是一个指向fun函数的指针,调用t,相当于直接调用fun,套用以上规则,打印出来1自然很好理解了。
    3)使用apply和call
    apply: 调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数
    call: 使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
    注:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。

    function fun() {
    return this.a;
    }
    fun(); //1
    //严格模式
    fun.call(undefined)
    //非严格模式
    fun.call(window)
    
    

    这时候我们就可以解释下,为啥说在非严格模式下,当函数this指向undefined的时候,会自动指向全局对象,如上,在非严格模式下,当调用fun.call(undefined)的时候打印出来的依旧是1,就是最好的证据。

    为啥说是万能公式呢?再看函数作为对象的方法调用:

    var a = 1; 
    var obj = { 
     a: 2, 
     b: function() { 
      return this.a; 
     } 
    } 
    obj.b() 
    obj.b.call(obj)
    

    如上,是不是很强大,可以理解为其它两种都是这个方法的语法糖罢了,那么apply和call是不是真的万能的呢?并不是,ES6的箭头函数就是特例,因为箭头函数的this不是在调用时候确定的,这也就是为啥说箭头函数好用的原因之一,因为它的this固定不会变来变去的了。关于箭头函数的this我们稍后再说。

    4)作为构造函数

    function Fun() { 
     this.name = 'Damonre'; 
     this.age = 21; 
     this.sex = 'man'; 
     this.run = function () { 
      return this.name + '正在跑步'; 
     } } 
     Fun.prototype = { 
      contructor: Fun, 
      say: function () { 
        return this.name + '正在说话'; 
      } 
     } 
     var f = new Fun(); 
     f.run();//Damonare正在跑步 
     f.say();//Damonare正在说话
    

    如果函数作为构造函数用,那么其中的this就代表它即将new出来的对象。

    function Fun() { 
     //new做的事情 
    var obj = {}; 
    obj.__proto__ = Fun.prototype;//Base为构造函数 
    obj.name = 'Damonare'; 
    ...//一系列赋值以及更多的事 
    return obj 
    }
    

    也就是说new做了下面这些事:
    创建一个临时对象
    给临时对象绑定原型
    给临时对象对应属性赋值
    将临时对象return
    也就是说new其实就是个语法糖,this之所以指向临时对象还是没逃脱上面说的几种情况。

    当然如果直接调用Fun(),如下:

    function Fun() { 
     this.name = 'Damonre'; 
     this.age = 21; 
     this.sex = 'man'; 
     this.run = function () { 
      return this.name + '正在跑步'; 
     } } 
    Fun(); 
    console.log(window)
    
    

    其实就是直接调用一个函数,this在非严格模式下指向window,你可以在window对象找到所有的变量。
    另外还有一点,prototype对象的方法的this指向实例对象,因为实例对象的proto已经指向了原型函数的prototype。这就涉及原型链的知识了,即方法会沿着对象的原型链进行查找。

    箭头函数
    刚刚提到了箭头函数是一个不可以用call和apply改变this的典型。
    我们看下面这个:

    var a = 1; 
    var obj = { a: 2 }; 
    var fun = () => console.log(this.a); 
    fun();//1 
    fun.call(obj)//1
    
    

    以上,两次调用都是1。
    那么箭头函数的this是怎么确定的呢?箭头函数会捕获其所在上下文的 this值,作为自己的 this值,也就是说箭头函数的this在词法层面就完成了绑定。apply,call方法只是传入参数,却改不了this。

    var a = 1; 
    var obj = { a: 2 }; 
    function fun() { 
      var a = 3; 
      let f = () => console.log(this.a); 
      f(); 
    }; 
    fun();//1 
    fun.call(obj);//2
    
    

    如上,fun直接调用,fun的上下文中的this值为window,注意,这个地方有点绕。fun的上下文就是此箭头函数所在的上下文,因此此时f的this为fun的this也就是window。当fun.call(obj)再次调用的时候,新的上下文创建,fun此时的this为obj,也就是箭头函数的this值。
    再来一个:

    function Fun() { 
      this.name = 'Damonare'; 
    } 
    Fun.prototype.say = () => { console.log(this); } 
    var f = new Fun(); 
    f.say();//window
    
    

    有的同学看到这个会很懵逼,感觉上this应该指向f这个实例对象啊。不是的,此时的箭头函数所在的上下文是proto所在的上下文也就是Object函数的上下文,而Object的this值就是全局对象。
    那么再来一个:

    function Fun() { 
      this.name = 'Damonare'; 
      this.say = () => { console.log(this); } 
    } 
    var f = new Fun(); 
    f.say();//Fun的实例对象
    
    

    如上,this.say所在的上下文,此时箭头函数所在的上下文就变成了Fun的上下文环境,而因为上面说过当函数作为构造函数调用的时候(也就是new的作用)上下文环境的this指向实例对象。

    相关文章

      网友评论

        本文标题:关于this

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