一文彻底搞懂js中this指向问题

2,421 阅读6分钟

先简单的总结一下

JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变

  • 在对象方法中,this表示该方法所属的对象(this 指向调用它所在方法的对象)
  • 如果单独使用this表示全局对象,非严格模式下(node环境下:Global;浏览器下:Window)
  • 在函数使用中,this 指向函数的所属者(严格模式下:函数是没有绑定到 this 上,这时候 this 是未定义的undefined)
  • 在事件中,this表示接收事件的元素(在 HTML 事件句柄中,this 指向了接收事件的 HTML 元素)
  • 类似call()和apply()方法可以将this引用到任何对象中(apply 和 call 允许切换函数执行的上下文环境(context),即 this 绑定的对象,可以将 this 引用到任何对象)

终极秘籍

  1. 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。

  2. 如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

  3. 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象

针对以上三点举几个例子:

例1:

function a(){
    var user = "阳光明媚";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();

例2:

function a(){
    var user = "阳光明媚";
    console.log(this.user); //undefined
    console.log(this);  //Window
}
window.a();

例1和例2是一样的,函数a是被Window对象所点出来:

例3:

var o = {
    user:"阳光明媚",
    fn:function(){
        console.log(this.user);  //阳光明媚
    }
}
o.fn();

例3这里的this指向的是对象o,因为调用这个fn是通过o.fn()执行的,那自然指向就是对象o

例4:

var o = {
    user:"阳光明媚",
    fn:function(){
        console.log(this.user); //阳光明媚
    }
}
window.o.fn();

例3和例4几乎是一样的,但是这里的this为什么不是指向window,看下面的例子

例5:

var o = {
    a:8,
    b:{
        a:15,
        fn:function(){
            console.log(this.a); //15
        }
    }
}
o.b.fn();

例6:

var o = {
    a:8,
    b:{
        // a:15,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

还有一种比较特殊的情况

例7:

var o = {
    a:8,
    b:{
        a:15,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

这里this指向的是window,是不是有懵逼?其实是因为你没有理解一句话,这句话同样至关重要。

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子7中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子4是不一样的,例子4是直接执行了fn。

this讲来讲去其实就是那么一回事,只不过在不同的情况下指向会有不同而已。

构造函数版this:

function Fn(){
    this.user = "阳光明媚";
}
var a = new Fn();
console.log(a.user); //阳光明媚

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

当this碰到return时

function fn(){  
    this.user = '阳光明媚';  
    return {};  
}
var a = new fn;  
console.log(a.user); //undefined
function fn(){  
    this.user = '阳光明媚';  
    return function(){};
}
var a = new fn;  
console.log(a.user); //undefined
function fn(){  
    this.user = '阳光明媚';  
    return 15;
}
var a = new fn;  
console.log(a.user); //阳光明媚
function fn(){  
    this.user = '阳光明媚';  
    return undefined;
}
var a = new fn;  
console.log(a.user); //阳光明媚

通过以上几个例子,也就是说如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

function fn(){  
    this.user = '阳光明媚';  
    return undefined;
}
var a = new fn;  
console.log(a); //fn {user: "阳光明媚"}

还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null是基本数据类型。

function fn(){  
    this.user = '阳光明媚';  
    return null;
}
var a = new fn;  
console.log(a.user); //阳光明媚

new操作符会改变函数this的指向问题

function fn(){
    this.num = 1;
}
var a = new fn();
console.log(a.num); //1

为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

let fn = function(){
  alert(this.name)
}
let obj = {
  name: '',
  fn
}
fn() // 方法1
obj.fn() // 方法2
fn.call(obj) // 方法3
let instance = new fn() // 方法4
  1. 方法1中直接调用函数fn(),这种看着像光杆司令的调用方式,this指向window(严格模式下是undefined)。
  2. 方法2中是点调用obj.fn(),此时this指向obj对象。点调用中this指的是点前面的对象。
  3. 方法3中利用call函数把fn中的this指向了第一个参数,这里是obj。即利用callapplybind函数可以把函数的this变量指向第一个参数。
  4. 方法4中用new实例化了一个对象instance,这时fn中的this就指向了实例instance

如果同时发生了多个规则怎么办?其实上面四条规则的优先级是递增的:

fn() < obj.fn() < fn.call(obj) < new fn()

首先,new调用的优先级最高,只要有new关键字,this就指向实例本身;接下来如果没有new关键字,有call、apply、bind函数,那么this就指向第一个参数;然后如果没有new、call、apply、bind,只有obj.foo()这种点调用方式,this指向点前面的对象;最后是光杆司令foo() 这种调用方式,this指向window(严格模式下是undefined)。

es6中新增了箭头函数,而箭头函数最大的特色就是没有自己的this、arguments、super、new.target,并且箭头函数没有原型对象prototype不能用作构造函数(new一个箭头函数会报错)。因为没有自己的this,所以箭头函数中的this其实指的是包含函数中的this。无论是点调用,还是call调用,都无法改变箭头函数中的this