首先,很多人对this有两个误解

  1.this指向的是当前函数本身

  什么意思呢,下面看一段代码。

 1 function func(){
 2     this.a ++;
 3 }
 4             
 5 var a = 0;
 6 func.a = 0;
 7             
 8 for(let i = 0; i < 5; i++){
 9     func();
10 }
11             
12 console.log(a);  //->5
13 console.log(func.a);//->0

    发现问题了,因为函数本身也是一个对象,如果this指向的是当前函数本身,那么在调用func函数5次后,func.a应该输出的是5,但是程序结果输出的是0,但是全局定义的a变量输出了结果5,显然在这里函数中this指向的不是函数本身,而指向了全局对象.

    这是为什么呢?接着往下看。

   

  2.this指向的是当前函数的作用域

  先看一段代码:

function foo(){
    var a = 0;
    this.bar();          
}

function bar(){
    console.log(this.a);
}

foo();//->undefined

 

  这段代码在按照“this指向的是当前函数的作用域”这样的理解下,在理想的执行状态下是这样子的:

//foo();

function foo(){
    var a = 0;
    console.log(a);  
}

  因为按照上文理解,this指向的是当前的函数作用域,所以这段代码能够执行成功是有问题的,如果this指向的是当前的函数作用域,foo函数的词法作用域中并没有一个标识符为bar的函数;

  在严格模式下,这段代码是无法执行的(报错)。

  

  

  this到底是一个什么样的机制呢?那么这里下一个定义:this的指向是取决于函数调用的方式,跟函数怎么写没有半毛钱关系。this的指向并没有在编写代码的时候就绑定了,而发生在函数的调用时。

  当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

  

函数调用位置的追踪:

function baz(){
    //当前的调用栈是:baz
    //因此,当前调用位置是全局作用域

    console.log("baz");  
    bar(); // <-- bar的调用位置
}

function bar(){
    //当前调用栈是:baz -> bar
    //因此,当前调用位置在baz中
    console.log("bar");
    foo();  // <-- foo的调用位置  
}

function foo(){
    //当前的调用栈是:baz -> bar -> foo
    //因此,当前的调用位置在bar中
    console.log("foo");    
}

baz();  //<-- baz的调用位置

 

既然this的绑定取决于函数的调用方式,那么this存在下面这四种绑定规则:

  • 默认绑定
  • 隐式绑定
  • 显示绑定
  • new绑定

 

默认绑定简单地来讲,默认绑定就是单独的,没有任何修饰符的函数调用,就像这样。

foo();

这时,this指向全局对象,下面给这个函数加上this来试一试。

function foo(){
    console.log(this.num);
}

var num = 6;

foo(); //->6

但是,只有在非严格模式下,this在默认绑定下才能绑定到全局对象上;如果在严格模式下,this会绑定到undefined

记住了,就光写一个光秃秃的函数调用,就是默认绑定,这时this指向的是全局对象(在非严格模式下)。

 

隐式绑定:又是简单地来讲,隐式绑定就是当这个函数作为某个对象的方法被调用时,this就会指向这个对象。

 

obj.foo();

 

考虑一下调用的位置是否有上下文对象,就是说这个函数是否被某个对象拥有或包含。

function foo(){
    console.log(this.a);
}

var obj = {
    a : 2,
    foo : foo
};

obj.foo(); // -> 2 这时foo函数的this指向obj这个对象

因为这时this函数指向的就是包含它的obj对象,所以此时this.a === obj.a 

这就叫做隐式绑定,不过隐式绑定有一个问题,最常见的问题就是被隐式绑定的函数会丢失绑定对象,怎么回事呢,来看一下下面的代码:

function foo(){
    console.log( this.a );
}

var obj = {
    a : 2,
    foo : foo  
};

var bar = obj.foo;

var a = "global";  //a是全局对象的属性

bar();  // -> "global"

看到没?本来foo应该指向obj来着,现在指向了全局对象,是怎么回事呢?那是因为obj的方法foo作为一个单独的函数被赋值给了bar,bar就成了foo函数的别名,输出一下bar,看看里面是什么:

这里可以看到,bar的内容是foo函数的函数体,所以这里调用bar,与foo()语句等价,使用了默认绑定,this指向全局对象,所以才会输出"global",在编程中,要注意这个细节。

 

显式绑定:显式绑定说白了就是函数使用call方法或apply显式地指定了this的指向。

例如:

function foo(){
    console.log( this.a );
}

var obj = {
    a : 2
};

foo.call( obj );  //->2

说到了call和apply,他俩也没啥区别,只是调用时写法有点不一样:

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2]);

call的函数参数一直写,中间用逗号隔开,apply的函数参数以数组形式给出。

 

new绑定:这个就不用说了,在用new调用函数时,函数内的this指向当前对象。

在JavaScript中,没有明显的标识符什么的说明构造函数是构造函数,实际上,Javascript不存在构造函数,只有被“构造调用”的普通函数。new关键字将一个普通函数“构造调用”,并且将这个普通函数的this与创建的对象相关联。

 

function Person( name, sex ){
    this.name = name;
    this.sex = sex;
}

// -> Person的this现在指向xiaoming
var xiaoming = new Person("小明","男");  

 

 

那么基本的this指向问题就这么简单地说完了,总结一下,this的指向其实跟函数如何编写没有什么关系,只跟函数调用的方式有关系。希望这篇文章可以帮到大家更好地理解this的问题,如果有补充或者疑问,请在评论区留言,谢谢!