“这个” 指的是 “谁”:浅解 this指针

305 阅读6分钟

前言

this这个玩意说实话,要是真的详细解释的话,三天三夜都说不完,而在各种应接不暇的书籍当中,对this也有较多的详解。可以说this在JavaScript当中简直是无处不在,应用范围十分广泛,下面我们就来好好了解一下this

“这个”this

定义

我们都知道js执行的时候会产生一个全局上下文,而函数在执行的时候也会产生函数的执行上下文。当一个函数被调用时,会创建一个执行上下文,这个执行上下文包含 函数在哪里被调用(调用栈),函数的调用方法以及传入的参数,this就是用来记录这个执行上下文信息的一个属性。

this“归属”

那么this的归属是谁呢?

简单一句话来概括,当前方法的最直接的环境上下文,采用就近原则。也就是函数的执行在调用栈中处在哪里,函数的this指向哪里 。(不考虑严格模式)

全局上下文

console.log(this === window) //true

var a = 4

console.log(this.a) //4

在浏览器中,无论是否在严格模式下,我们的this在全局上下文都是指向我们的window(因为在全局上下文被调用),而由于在node环境当中没有window这个概念,所以打印结果就会是undefined

函数上下文

function foo(){

   //use strict 严格模式
   
    console.log(this.a) //2
    
}

var a = 2

foo()

foo()函数在全局被调用,所以foo函数当中的this同样也是指向window,那么this.a相当于是window.a,输出的结果自然是2。

在这里是默认绑定,而在严格模式下,this和他调用的作用域没有关系,this就会被绑定为undefined

我们再看一段代码,深刻理解一下this的指向

var a = 1

function baz(){

    //此刻调用的位置是调用栈的全局 this 指向全局window
    
    let a = 2

    console.log('this.a');

    bar()

}

function bar(){

    //调用位置是调用栈的 baz -> bar  this指向baz

      let a = 3

    console.log('this.a');

    foo()

}

function foo(){

    //调用位置是调用栈的 baz -> bar -> foo  this指向bar

      let a = 4

    console.log('this.a');

}

baz() // baz调用

代码分析

我们来分析一下上述代码,看看三个打印,思考一下,打印出来的this.a是什么。

在全局上下文当中,我们定义了baz()、bar()、foo()三个函数,baz()在全局上下文中被调用,所以它的this指针指向window;而bar()在baz()当中被调用,this指向的是baz函数的执行上下文;而foo()同理,它内部的this指向了bar()的执行上下文,那么这么层层嵌套下来,最终他们的this都等同于指向了window,也就是说打印的时候this.a等同于window.a(但是事实上不是这样,this实际上指向所在方法被调用的函数上下文),所以打印结果都为 1。

当然,在JavaScript当中我们还有几种方法人为的绑定this,分别是apply(),bind() call(),在后续中我们将会说到。

对象中的this

 function foo() {

    console.log(this.a);

 }

var obj1 = {

    a: 2,

    obj2: obj2

}
var obj2 = {

    a: 4,

    foo: foo  //key属性:foo    引用foo this绑定到对象obj1

}

obj1.obj2.foo()    //4     函数foo 在哪被引用,this指向哪 this指向obj2 a = 4



代码分析

我们定义了函数foo()和两个对象obj1,obj2,将obj2作为obj1的key属性,将obj2对象作为value值,而foo作为obj2的key属性,引用foo,将函数foo作为其value值。然后利用obj1.obj2.foo()调用这个foo(). 打印的结果是4,这是为什么呢?

【隐式绑定】

这就是我们的隐式绑定规则了,当函数引用时有上下文对象,那么就会隐式绑定,把函数的this绑定带该上下文上面。将函数foo()当做引用属性添加到了obj2对象当中去,但是严格意义上来说foo函数是不属于obj2对象的,但是执行的时候,foo()函数在obj2的执行上下文被调用,因此你可以说函数被调用时“拥有”或者“包含”函数引用。所以foo()函数的this指向的是obj2,那么打印出来的结果就为2了

【知识点】

将方法的调用最内层包裹在一个指定上下文里,若是后面再进行绑定,均不能插入到这一层环境中间。上述例子中,foo()的this已经绑定了obj2,若是将obj2整体绑定到别的对象当中,foo()函数的this依然指向obj2.

【隐式丢失】

function foo(){

console.log(this.a); 

}

var obj = { 

a:2, 

foo:foo //隐式绑定 

} 
var bar = obj.foo //  函数别名  实际上调用的是函数本身

var a = 4 

bar() // 4 调用的是全局下面的a

代码分析

分析上述代码,定义了obj对象以及foo()函数,obj对象将foo作为函数引用,也就是隐式绑定。而后我们在外部定义了变量a和bar,那么最后打印的结果会是什么呢?

在这里我们需要注意的是,var bar = obj.foo这个函数表达式,因为bar只是绑定了fn函数的引用,因此bar只是一个函数的调用,也就是说实际上是在调用foo()函数本身,会应用默认绑定绑定到了全局window,所以打印结果会是4.

这种情况也会出现在回调函数当中,所以我们应当注意,莫要踩坑!

【显式绑定】

 function foo() {

     console.log(this.a);

 }

 var obj = {

    a: 2,

}

 foo.call(obj) //人为绑定

实际上就是利用我们的call(),apply(),bind()函数来指定this的执行上下文,用法也比较简单,但是这种经常会在面试当中提到,后续我会详细讲解他们三者的区别。

构造函数

我们都知道构造函数在创建一个对象的时候都要经历三部曲

构造函数三部曲

1 创建this对象

2 挂上对象该有的属性值,name属性传值,对象的隐式原型绑定构造函数显示原型

3 返回this对象

我们来看简单的实例

function constructor(){

  this.a = 1;
  
}

var obj = new constructor();

console.log(obj.a); //1

当一个函数用作构造函数时,使用new关键字时,它的this被绑定到正在构造的新对象中。所以自然而然就会打印出1了。

总结

this的变化无穷无尽,也是耗费了数以万计的程序员才总结出来的。就算把我给搞秃头了,都说不完this的故事,以上是我对于this的浅解,总之一句话,谁包住了this,那么就得对this负责,当然,不考虑严格模式哈。

我是小白,浅解this,如有错误,还请各位指出,谢谢!