JS的this指向问题

290 阅读3分钟

关于js中this的指向问题,我们首先要了解一下几个概念:

  • 全局变量默认挂载在window对象下
  • 一般情况下this指向它的调用者
  • es6的剪头函数中,this指向创建者,并非调用者
  • 通过call,apply,bind可以改变this的指向

在函数调用时

非严格模式

const fn1 = function () {
    console.log(this);
    const fn2 = function() {
        console.log(this);
    }
    fn2();//window
}
fn1();//window

严格模式下

'use strict'
const fn1 = function () {
    console.log(this);
    const fn2 = function() {
        console.log(this);
    }
    fn2();//undefined
}
fn1();//undefined

结合第一和第四条规则:fn1这个函数是全局的,默认挂载在window对象下,this指向它的调用者即window,所以输出window对象,但是在严格模式下,this不允许指向全局变量window,所以输出为undefined(fn2在函数直接调用时默认指向了全局window,其实这属于JavaScript设计上的缺陷,正确的设计方式是内部函数的this应该绑定到外层函数对应的对象上,为了规避这一设计缺陷,程序员就想出了变量替代的方法,约定俗成,该变量一般被命名为that。)

作为对象方法

const obj = {
    name:'roy',
    age:18,
    infoFun:function(){
        console.log('我的名字叫:'+this.name+',年龄:'+this.age);
        const fn = function(){
            console.log('我的名字叫:'+this.name+',年龄:'+this.age);
        }
        fn();//我的名字叫:undefined,年龄:undefined
    }
}
obj.infoFun();//我的名字叫:roy,年龄:18

按照第二条规则,this指向它的调用者,infoFun()方法的调用者是obj,所以在infoFun()方法内部this指向了它的父对象即obj,而fn方法输出的为undefined的原因就是因为我们前面说的缺陷问题,这种情况下,我们通常选择在infoFun()方法里将this缓存下来。

const obj = {
    name:'roy',
    age:18,
    infoFun:function(){
        console.log('我的名字叫:'+this.name+',年龄:'+this.age);
        const that = this;
        const fn = function(){
            console.log('我的名字叫:'+that.name+',年龄:'+that.age);
        }
        fn();//我的名字叫:roy,年龄:18
    }
}
obj.infoFun();//我的名字叫:roy,年龄:18

此时fn的指向就理想了。

const obj = {
    name:'roy',
    age:18,
    infoFun:function(){
        console.log('我的名字叫:'+this.name+',年龄:'+this.age);
    }
}
const other = obj.infoFun;
other();//我的名字叫:undefined,年龄:undefined
const data = {
    name:'Tom',
    age:20
}
data.infoFun = obj.infoFun;
data.infoFun();//我的名字叫:Tom,年龄:20

在看这段代码,将infoFun()赋值给了全局变量other,调用other()方法,other挂载在全局函数window对象下,window对象下没有name和age这两个属性,所以输出为undefined。第二段代码,申明了data对象,包含了name和age属性,在第二条规则一般情况下this指向它的调用者,大家就明白了,data是infoFun()的函数的调用者,所以输出了data的name和age。

在html里作为事件触发

<body>
  <div id="btn">点击我</div>
</body>
<script>
    const btn=document.getElementById('btn');
    btn.addEventListener('click',function () {
      console.log(this); //<div id="btn">点击我</div>
    })
</script>     

在这种情况下其实也遵循了第二条规则一般情况下this指向它的调用者,this指向了事件的事件源即event。

new关键字(构造函数)

const fn=function(name){
    this.name=name;
}
const obj=new fn('roy');  
console.log(obj.name); //roy

new关键字构造了一个对象实例,赋值给了obj,所以name就成为了obj对象的属性。

es6(剪头函数)

const fn1=()=>{
   console.log(this); 
};
fn1(); //Window
const data={
    name:'roy',
    infoFun:function(){
      console.log(this); //Object {name: "roy", infoFun: function}
      const fn2=()=>{
        console.log(this); //Object {name: "roy", infoFun: function}
      }
 
      fn2();
    }
  }
  data.infoFun();

根据第三条规则:es6的剪头函数中,this指向创建者,并非调用者,fn1在全局函数下创建,所以this指向全局window,而fn2在对象data下创建,this指向data对象,所以在fn2函数内部this指向data对象。

改变this的指向

call,apply,bind这三个函数是可以人为的改变函数的this的指向。

const fn=function(){
   console.log(this);
 }; 
 fn(); //window
 fn.apply({name:"roy"}); //Object {name: "roy"}