JavaScript中的this使用详解
其实this是一个老生常谈的问题了。关于this的文章非常多,其实我本以为自己早弄明白了它,不过昨天在做项目的过程中,还是出现了一丝疑惑,想到大概之前在JavaScriptweekly里收藏待看的一篇详解this的文章(后有链接,也附上了稀土上的中文译文)和另一篇一位前辈推荐的文章,就把它们看了看,对this的认识确实提升了一些。
JavaScript中的'this‘是动态的,它在函数运行时被确定而非在函数声明时被确定。所有的函数都可以调用'this',这无关于该函数是否属于某个对象。关于this,主要有以下四种情况。
1.被当做对象的方法被调用
如果该函数是被当做某一个对象的方法,那么该函数的this指向该对象;
varjohn={
firstName:"John"
}
functionfunc(){
alert(this.firstName+":hi!")
}
john.sayHi=func
john.sayHi()//this=john
这里有一点值得注意,当一个对象的方法被取出来赋值给一个变量时,该方法变为函数触发,this指向window或underfind(严格模式)。
2.函数之内调用
当函数中有this,其实就意味着它被当做方法调用,之间调用相当于把他当做window对象的方法,this指向window,值得注意的是ES5其实是规定这种情况this=undefined的,只浏览器大多还是按照老的方法执行(本人在最新版的Chrome,Safari,Firefox中测试都指向window(201607)),在火狐下使用严格模式指向undefined;
func()
functionfunc(){
alert(this)//[objectWindow]or[objectglobal]orkindof..
}
为了传递this,()之前应该为引用类型,类似于obj.a或者obj['a'],不能是别的了。
这里还存在一个小坑,当对象的方法中还存在函数时,该函数其实是当做函数模式触发,所以其this默认为window(严格模式下为undefined)解决办法是给该函数绑定this。
varnumbers={
numberA:5,
numberB:10,
sum:function(){
console.log(this===numbers);//=>true
functioncalculate(){
//thisiswindoworundefinedinstrictmode
console.log(this===numbers);//=>false
returnthis.numberA+this.numberB;
}
returncalculate();
}
};
numbers.sum();//=>NaNorthrowsTypeErrorinstrictmode
varnumbers={
numberA:5,
numberB:10,
sum:function(){
console.log(this===numbers);//=>true
functioncalculate(){
console.log(this===numbers);//=>true
returnthis.numberA+this.numberB;
}
//use.call()methodtomodifythecontext
returncalculate.call(this);
}
};
numbers.sum();//=>15
3.在new中调用
一个引用对象的变量实际上保存了对该对象的引用,也就是说变量实际保存的是对真实数据的一个指针。
使用new关键字时this的改变其实有以下几步:
创建this={}.
new执行的过程中可能改变this,然后添加属性和方法;
返回被改变的this.
functionAnimal(name){
this.name=name
this.canWalk=true
}
varanimal=newAnimal("beastie")
alert(animal.name)
需要注意的是如果构造函数返回一个对象,那么this指向返回的那个对象;
functionAnimal(){
this.name='Mousie';
this.age='18';
return{
name:'Godzilla'
}//<--willbereturned
}
varanimal=newAnimal()
console.log(animal.name)//Godzilla
console.log(animal.age)//undefined
这里需要注意的是不要忘记使用new,否则不会创建一个新的函数。而是只是执行了函数,相当于函数调用,this其实指向window
functionVehicle(type,wheelsCount){
this.type=type;
this.wheelsCount=wheelsCount;
returnthis;
}
//Functioninvocation
varcar=Vehicle('Car',4);
car.type;//=>'Car'
car.wheelsCount//=>4
car===window//=>true
4.明确调用this,使用call和apply
这是最具JavaScript特色的地方。
如下代码:
func.call(obj,arg1,arg2,...)
第一个参数将作为this的指代对象,之后的参数将被作为函数的参数,解决方法是使用bind。
functionAnimal(type,legs){
this.type=type;
this.legs=legs;
this.logInfo=function(){
console.log(this===myCat);//=>true
console.log('The'+this.type+'has'+this.legs+'legs');
};
}
varmyCat=newAnimal('Cat',4);
//logs"TheCathas4legs"
setTimeout(myCat.logInfo.bind(myCat),1000);
//setTimeout??
varjohn={
firstName:"John",
surname:"Smith"
}
functionfunc(a,b){
alert(this[a]+''+this[b])
}
func.call(john,'firstName','surname')//"JohnSmith"
至于apply,其只是以数组的方传入参数,其它部分是一样的,如下:
func.call(john,'firstName','surname') func.apply(john,['firstName','surname'])
它们也可用于在ES5中的类继承中,调用父级构造器。
functionRunner(name){
console.log(thisinstanceofRabbit);//=>true
this.name=name;
}
functionRabbit(name,countLegs){
console.log(thisinstanceofRabbit);//=>true
//间接调用,调用了父级构造器
Runner.call(this,name);
this.countLegs=countLegs;
}
varmyRabbit=newRabbit('WhiteRabbit',4);
myRabbit;//{name:'WhiteRabbit',countLegs:4}
5..bind()
对比方法.apply()和.call(),它俩都立即执行了函数,而.bind()函数返回了一个新方法,绑定了预先指定好的this,并可以延后调用。
.bind()方法的作用是创建一个新的函数,执行时的上下文环境为.bind()传递的第一个参数,它允许创建预先设置好this的函数。
varnumbers={
array:[3,5,10],
getNumbers:function(){
returnthis.array;
}
};
//Createaboundfunction
varboundGetNumbers=numbers.getNumbers.bind(numbers);
boundGetNumbers();//=>[3,5,10]
//Extractmethodfromobject
varsimpleGetNumbers=numbers.getNumbers;
simpleGetNumbers();//=>undefinedorthrowsanerrorinstrictmode
使用.bind()时应该注意,.bind()创建了一个永恒的上下文链并不可修改。一个绑定函数即使使用.call()或者.apply()传入其他不同的上下文环境,也不会更改它之前连接的上下文环境,重新绑定也不会起任何作用。
只有在构造器调用时,绑定函数可以改变上下文,然而这并不是特别推荐的做法。
6.箭头函数
箭头函数并不创建它自身执行的上下文,使得this取决于它在定义时的外部函数。
箭头函数一次绑定上下文后便不可更改,即使使用了上下文更改的方法:
varnumbers=[1,2];
(function(){
varget=()=>{
console.log(this===numbers);//=>true
returnthis;
};
console.log(this===numbers);//=>true
get();//=>[1,2]
//箭头函数使用.apply()和.call()
get.call([0]);//=>[1,2]
get.apply([0]);//=>[1,2]
//Bind
get.bind([0])();//=>[1,2]
}).call(numbers);
这是因为箭头函数拥有静态的上下文环境,不会因为不同的调用而改变。因此不要使用箭头函数定义方法
functionPeriod(hours,minutes){
this.hours=hours;
this.minutes=minutes;
}
Period.prototype.format=()=>{
console.log(this===window);//=>true
returnthis.hours+'hoursand'+this.minutes+'minutes';
};
varwalkPeriod=newPeriod(2,30);
walkPeriod.format();//=>'undefinedhoursandundefinedminutes'
参考
Fourscentsof"this"
Gentleexplanationof'this'keywordinJavaScript
JavaScriptThis之谜(译文)
强烈推荐觉得没弄明白的同学看看上面三篇文章,其中第三篇是第二篇的译文。如果大家对this还有疑问,也欢迎大家一起讨论,交流促进思考,共同进步。