JS 中 this 的指向

1,695 阅读6分钟

为什么要使用this? 解决的问题?

可以先通过一个例子了解下

function speak(){
    var name = this.name
    console.log("Hello I am --" + name)
}
var me = {
    name: 'a',
    speak: speak
}
var you = {
    name: 'b',
    speak: speak
}
me.speak()  //Hello I am -- a
you.speak()  //Hello I am -- b

this可以在同一个执行环境中使用不同的上下文对象。它其实提供了一种更加优雅的方式来隐式“传递”一个对象引用,因此可以使API设计的更加简洁且易于复用。

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

其实关于this的指向问题可以从函数的执行调用过程中理解,当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用、函数的调用方法、传入的参数等信息,this也是这里的一个属性。当函数被某个对象调用时可以理解为在函数调用的那一刻它被调用对象拥有。所以this指向调用其的对象。

对于this的指向问题,一般都是根据以上的解释去理解,虽然在一般的情况下这样的理解是不会有问题的,但是当在某些场景下这样的解释并不是很准确,所以会让人感觉一直琢磨不透的感觉。今天可以就不同情况展开讨论下this指向的问题。看看下面的打印结果会是什么

// 例子1
function test(){
    var a = 10;
    console.log(this.a);
    console.log(this);
}
test();
// 例子二
var b = 10;
var o = {
    b: 20,
    fn:function(){
        console.log(this.b); 
    }
}
o.fn();

按照上面的定义this最终指向的是调用它的对象,这里的函数test实际是被Window对象所点出来的。所以例子1中的this指向的是windows。在例子2中函数的执行是通过o.fn()调用的,所以this的指向的当然是对象o了。这两个例子可以验证上面的定义,但是还是不够准确的。

// 例子三
var b = 10;
var o = {
    b: 20,
    fn:function(){
        console.log(this.b); 
    }
}
window.o.fn();

此时打印的应该是什么呢?这段代码和上面的那段代码几乎是一样的,但是这里的this为什么不是指向window,如果按照上面的理论,最终this指向的是调用它的对象,在此处就显得不是很准确了。所以关于函数中this的指向其实是可以分为三种情况的

  • 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window
  • 如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
  • 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,这就可以解释例子三中的this不是指向window了

那么以下的打印结果又应该是什么?

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

此处的this指向的是window,其实这里只需要理解清楚一句话"this永远指向的是最后调用它的对象",也就是看它执行的时候是谁调用的,例子4中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子3是不一样的,例子3是直接执行了fn。所以说,那例子5中的打印结果又应该是什么呢?

// 例子5
function foo() { 
    console.log( this.a );
}
var obj = { 
    a: 2,
    foo: foo 
};
var bar = obj.foo; // 函数别名!
var a = "xxxxx"
bar();

不同情况下this的使用

1.构造函数版this

// 例子6
function Fn(){
    this.user = "lh";
}
var a = new Fn();
console.log(a.user);

这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象Fn中会有user,因为已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。

!当this遇上return时

// 例子7
function fn()  
{  
  this.user = 'lh';  
  return {};  
}
var a = new fn;  
console.log(a.user);

// 例子8
function fn()  
{  
  this.user = 'lh';  
  return function(){};
}
var a = new fn;  
  console.log(a.user)

// 例子9
function fn()  
{  
  this.user = 'lh';  
  return 1;
}
var a = new fn;  
console.log(a.user);

由上可知,如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

2.箭头函数中的this

var x=11;
var obj={
 x:22,
 say:()=>{
   console.log(this.x);
 }
}
obj.say();

箭头函数不是通过function关键字定义的,也就不遵循以上的this规则,而是“继承”外层作用域中的this指向。箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定。所谓的定义时候绑定,就是this是继承自父执行上下文!!中的this,比如这里的箭头函数中的this.x,箭头函数本身与say平级以key:value的形式,也就是箭头函数本身所在的对象为obj,而obj的父执行上下文就是window,因此这里的this.x实际上表示的是window.x,因此输出的是11。

改变this指向的几种方法(apply,call, bind)

var a = {
    user:"lh",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b();
  • 此时要改变this的指向,可以通过call方法
var a = {
  user:"lh",
  fn:function(){
    console.log(this.user);
   }
}
var b = a.fn;
b.call(a);

通过在call方法,给第一个参数添加要把b添加到哪个环境中,也就是说,this就会指向那个对象。 call方法除了第一个参数以外还可以添加多个参数,如下

var a = {
  user:"lh",
  fn:function(p1, p2){
    console.log(this.user)
    console.log(p1+p2)
   }
}
var b = a.fn;
b.call(a, 1, 3
  • 使用apply()方法,改变this指向的效果和call相似,在于他们传递的参数格式不同,第二个参数必须是一个数组,如下:
var a = {
  user:"lh",
  fn:function(p1, p2){
    console.log(this.user)
    console.log(p1+p2)
   }
}
var b = a.fn;
b.apply(a, [1, 3])

!当call和apply的第一个参数写的是null,那么this指向的是window对象

  • bind()方法

bind()方法也可用来改变this的指向,但是和call,apply方法在用法上有区别,如下:

var a = {
    user:"lh",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b.bind(a);

此时发现并不打印任何的结果,这是因为bind方法返回的是一个修改过后的函数,此时执行conole.log(b.bind(a))会得到的结果是:


ƒ () {
  console.log(this.user);
}

call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。并且也是可以像call和apply一样传递参数