JavaScript中的this常常让人捉摸不定,用着用着你可能不知道它跟谁跑了,举个例子:

var parent={
  name:"parent",
  getName:function(){
    return this.name;
  }
}
console.log(parent.getName());//"parent"
var getName=parent.getName;
console.log(getName());//undefined
总的来说,JavaScript中的this总是指向一个对象,至于指向哪个对象需要根据具体执行环境来定。

规则1:作为对象的方法调用(隐式绑定)

var name="me";
var parent={
  name:"parent",
  getName:function(){
    console.log(this.name);
  }
}
parent.getName();//"parent"

getName是parent对象的方法,此时this会隐式的绑定给它的宿主对象parent。

规则2:作为普通函数调用(默认绑定)

var name="me";
function whoIsParent(){
    console.log(this.name);
}
whoIsParent();//"me"

当函数作为普通函数而不是某个对象(非window)的属性时候,this总是指向全局对象window,或者你可以理解该普通方法是window对象的方法;

还是规则1中的例子,我们稍微改造下:

var name="me";
var parent={
  name:"parent",
  getName:function(){
    console.log(this.name);
  }
}
var getName=parent.getName;//注意
getName();

在规则1中代码的基础上加了一行代码,parent.getName被赋值给了变量getName,再调用getName方法时候,this不再指向它parent对象,而是指向了window。

注意,当一个对象的方法不再作为对象的属性调用时,该方法会变成一个普通函数,this的指向会发生变化,指向全局变量window。

再看一个非常常见的例子:

<body>
  <button id="button">点我</button>
</body>
var id="window";
var $button=document.getElementById('button');
$button.onclick=function(){
  var callback=function(){
    console.log(this.id)
  };
  callback();//"window"
}

常用的解决办法是用一个变量保存上级this的引用

var id="window";
var $button=document.getElementById('button');
$button.onclick=function(){
  var self=this;//保存引用
  var callback=function(){
    console.log(self.id)
  };
  callback();//"button"
}
注意,在严格模式下,普通函数内的this是不能默认指向window的
function fun(){
  "use strict";
  console.log(this);//undefined
}
fun();

规则3:作为构造器调用(new)

function Parent(){
  this.name="parent";
}

var child=new Parent();
console.log(child.name);//"parent"

当使用new创建一个实例时,this的指向就已经确定了,它总是指向该实例。

注意:当使用new创建实例时,需注意一个问题,如果构造函数显示的返回了一个object类型的对象,那么new()的结果是返回该对象,而不是this。
function Parent(){
  this.name="parent";
  return{
    age:30
  }
}

var child=new Parent();
console.log(child.name);//undefined

如果构造器函数没有显示的返回一个object对象或者不返回任何数据,就不会造成上述问题。

function Parent(){
  this.name="parent";
  return "new parent";
}

var child=new Parent();
console.log(child.name);//"parent"

规则4:call,apply,bind显式指向

var parent={
  name:"parent",
  getName:function(){
    console.log(this.name);
  }
}

var obj={
  name:'child'
}

var getName=parent.getName.bind(obj);
getName();

这个比较好理解,显式的修改this指向,改哪指哪,parent.getName.bind(obj)显示的将getName方法指向obj调用。

规则5:箭头函数在定义时this就已经确定

function Parent(){
  this.name="parent";
  this.getName=function(){
    console.log(this.name);
  }
}

var child=new Parent();
var getName=child.getName;
getName();//undefined,作为普通函数调用,所以this指向window

稍加修改:

function Parent(){
  this.name="parent";
  this.getName=()=>{
    console.log(this.name);
  }
}
var child=new Parent();
child.name="child";
var getName=child.getName;
getName();//"child"

修改后this.getName是一个箭头函数,定义的时候this指向child,所以this.name等于"child"。

再看个例子:

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
var id = 21;
foo();//21

foo作为普通函数调用,所以this指向window,setTimeout中箭头函数定义时this指向window,所以输出21;

再看个例子:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 2000);//s1:2
setTimeout(() => console.log('s2: ', timer.s2), 2000);//s2:0

上面两个setInterval,一个用了箭头函数,一个用了普通函数,箭头函数定义时this指向timer,所以this.s1++相当于timer.s1++,结果为2,而普通函数的this指向window,timer.s2始终为0;

总结一下:

JavaScript中的this到底指向谁,得根据具体情况而定:
1、作为对象方法调用,this指向该对象;
2、作为普通函数调用,this指向全局对象window;
3、作为new实例调用,如果构造函数没有显示返回object类型数据,则指向该实例,反之指向被返回的object;
4、call,apply,bind能显式修改this指向;
5、箭头函数中this指向定义时所在的对象,而不是调用时所在的对象;
如果你的代码中this指偏了,考虑一下是上面哪种情况吧~


沉默术士
29 声望1 粉丝