JavaScript的“this”到底咋回事儿?

this到底是咋回事儿?

1. 概念

先来看看ECMAScript 标准规范对this 的定义:

「The this keyword evaluates to the value of the ThisBinding of the current execution context.」
「this 这个关键字代表的值为当前执行上下文的ThisBinding。」

然后再来看看MDN 对this 的定义:

「In most cases, the value of this is determined by how a function is called.」
「在大多数的情况下,this 其值取决于函数的调用方式。」

this是在函数被调用时发生的绑定,它指向什么取决于函数的调用方式。

2. 全局对象和调用普通函数

在全局环境中,this 指向全局对象,在浏览器中,它就是 window 对象。下面的示例中,无论是否是在严格模式下,this 都是指向全局对象。

 var a = 100;
 console.log(this.a)  //100
 console.log(this.a === a)  //true
 console.log(this === window)  //true

如果普通函数是在全局环境中被调用,在非严格模式下,普通函数中 this 也指向全局对象;如果是在严格模式下,this 将会是 undefined。ES5 为了使 JavaScript 运行在更有限制性的环境而添加了严格模式,严格模式为了消除安全隐患,禁止了 this 关键字指向全局对象。

var a = 100;
function foo(){
  console.log(this) //window
  console.log(this.a) //100
}

看下面这张图

image-20210127104153005

3. 作为对象的方法调用

我们一般把对象里基本类型的值叫属性(字符串String、数值Number、布尔值Boolean),如果值是函数,我们称之为方法。

当函数作为对象的方法被调用时,函数中的this指向这个调用的对象

image-20210127105416635

var a = 100;
var obj = {
  a: 200,
  foo: function(){
    console.log(this)
    console.log(this.a)
  }
}

obj.foo()
// {a: 200, foo: ƒ}
// 200
var fn = obj.foo; 
fn()
// Window
// 100

在上面代码中,运行obj.foo()时,foo方法的执行对象是obj对象,所以 this指向obj。而当把obj.foo赋值给fn变量时,相对于创建了全局函数fn,所以执行fn(),this指向Window对象。

再看一个案例:

 var a = 100
 var obj = {
   a: 200,
   b: {
     a: 300,
     foo: function () {
       console.log(this); // Object {a: 300, foo: function}
       console.log(this.a); // 300
     }
   }
 }

 obj.b.foo();

执行obj.b.foo()时,foo的调用者变成了b对象,所以this也就指向了b对象。这个案例是在对象中嵌套对象,那如果我们在函数中嵌套函数呢?

 var obj = {
   b: function () {
     console.log(this === obj); // true
     console.log(this); // {b: f}
     // 执行foo函数
     foo();

     function foo() {
       console.log(this === obj); // false
       console.log(this); // Window 全局对象
     }
   }
 }

 obj.b();

分析下,执行obj.b()时,b函数的执行对象是obj,所以this指向obj对象,这个没问题。问题是,在函数内执行foo函数,这时foo函数内的this不会指向obj,foo内的this不会继承外面的函数。其实还是那句话,"谁调用了这个函数"
在非严格模式下,this指向全局对象Window,严格模式下指向undefined。

4. 作为构造函数调用

函数除了作为普通函数被调用外,可以使用new关键字,作为构造函数调用。这时候,在构造函数内,this指向实例对象。

function Person(){
  this.age = 20;
  // Person {age: 20}
  console.log(this) 
}

var p = new Person()
// 20
console.log(p.age)

如果我们在构造函数返回了引用类型的对象,this会指向这个返回的对象:

function Person(){
  this.age = 20;
  // Person {age: 20}
  console.log(this) 
  return{
    age: 30
  }
}

var p = new Person()
// 30
console.log(p.age)

但如果返回了一个基本类型的值,则this仍然指向实例对象。

function Person(){
  this.age = 20;
  // Person {age: 20}
  console.log(this) 
  return 30
}

var p = new Person()
// 20
console.log(p.age)

5. 通过call和apply方法调用

我们知道通过call和apply方法可以改变this的指向。call和apply方法的第一个参数就是函数运行时this的指向。如果没有传参数(参数为空)或者传入null、undefined,name默认this指向全局对象(非严格模式下)或者为undefined(严格模式下)

image-20210127112836754

call和apply的主要区别在于参数,call第二参数之后是参数列表,可以有多个参数,apply的第二个参数是一个数组。对于this的改变的作用是一样,下面以call方法为例:

var a = 1;

var obj = {
  a: 2
}

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

foo.call(obj)
// Object {a: 2}
// 2

foo.call()
// Window 全局对象
// 1

foo.call(null)
// Window 全局对象
// 1

foo.call(undefined)
// Window 全局对象
// 1

使用 call 和 apply 时,如果给 this 传的不是对象,JavaScript 会使用相关构造函数将其转化为对象,比如传 number 类型,会进行new Number()操作,如传 string 类型,会进行new String()操作,如传 boolean 类型,会进行new Boolean()操作。

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

foo.call('Davie') // [object String]
foo.apply(100) // [object Number]
foo.call(true) // [object Boolean]

6. 通过bind方法调用

bind方法也可以改变函数的调用对象,和call及apply类似,但是call和apply是立即执行。而bind方法只是修改了函数的this指向,并不马上执行。

image-20210127115350628

var a = 100

var obj1 = {
	a: 200
};
var obj2 = {
	a: 300
};

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

var f1 = foo.bind(obj1);
var f2 = foo.bind(obj2);

foo();
// Window 全局对象
// 100

f1();
// Object {a: 200}
// 200

f2();
// Object {a: 300}
// 300

7. 箭头函数中的this

箭头函数没有自己的this绑定。箭头函数中使用的this,直接是包含它的那个函数或函数表达式中的this

修改前面函数嵌套的案例,把里面的函数写成箭头函数:

var obj = {
  b: function () {
    console.log(this === obj); // true
    console.log(this); // {b: f}

    var foo = () => {
      console.log(this === obj); // true
      console.log(this); //{b: f}
    }
    foo();

  }
}

obj.b();

这时箭头函数中的this就会继承完成函数的this,所以也指向了obj对象。

一句话:箭头函数的this继承自外层函数的this。如果有外层函数,那么外层函数的this就是箭头函数的this,如果没有就是全局对象Window。

var obj = {
  b: () => {
    console.log(this === obj); // false
    console.log(this); // Window 全局对象 

    var foo = () => {
      console.log(this === obj); // false
      console.log(this); // Window 全局对象 
    }
    foo();
  }
}

obj.b()

上例中,存在两个箭头函数,里面foo的this取决于最外层箭头函数b的this,由于obj是个对象而非函数,所以this指向为Window全局对象。

我们无法通过 bind、call 和 apply 来改变 this 的指向,即传入的第一个参数被忽略

var a = 100
var obj = {
  a: 200
}

var foo = () => {
  console.log(this.a)
  console.log(this)
}

foo.call(obj) // 不报错,只是obj被忽略了
// 100
// Window 全局对象

foo.apply(obj) // 不报错,只是obj被忽略了
// 100
// Window 全局对象

8. 总结

归根结底还是那句话,函数的this指向该函数的调用对象。

最后用一张图总结下this的指向:

this的指向总结

End

如果这篇文章有所帮助,请点点赞,点点关注,谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值