一直以来,this指向的问题都困扰着我,老是不清楚this到底是指向调用对象还是指向window,今天做个了结。
问题由下面这道题引出:
var obj={
id:"awesome",
cool:function coolFn(){
console.log(this.id);
}
};
obj.cool()//awesome
var id="not awesome";
setTimeout(obj.cool,0);//not awesome
关于以上,obj.cool输出的为“not awesome”,而非“awesome”,可这里的cool明明是obj调用的啊,问题就在于cool()函数失去了与this之间的绑定,即this指向丢失。
this总是指向调用的对象,就是说this指向谁与函数声明的位置没有关系,只与调用的位置有关。this的指向大概分为如下四种:
1.new绑定
new方式是优先级最高的一种调用方式,只要是使用new方式来调用一个构造函数,this一定会指向new调用函数新创建的对象:
function() thisTo(a){
this.a=a;
}
var data=new thisTo(2); //在这里进行了new绑定
console.log(data.a); //2
2.显式绑定
显示绑定指的是通过call()和apply()方法,强制指定某些对象对函数进行调用,this则强制指向调用函数的对象:
function thisTo(){
console.log(this.a);
}
var data={
a:2
};
thisTo.call(data)); //2
3.隐式绑定
隐式绑定是指通过为对象添加属性,该属性的值即为要调用的函数,进而使用该对象调用函数:
function thisTo(){
console.log(this.a);
}
var data={
a:2,
foo:thisTo //通过属性引用this所在函数
};
data.foo(); //2
4.默认绑定默认绑定是指当上面这三条绑定规则都不符合时,默认绑定会把this指向全局对象window:
function thisTo(){
console.log(this.a);
}
var a=2; //a是全局对象的一个同名属性
thisTo(); //2
以上四点是this在绑定调用对象时的规则,回到头部的那道题,该题看上去好像满足第三点诶,这不就是obj对象调用的函数么?this不就是应该指向obj对象本身么?实际则不然,在这个setTimeout的调用过程中,this的原本指向已经丢失了,丢失之后指向的即为全局的window。为什么会丢失呢?下面看下this在什么情况下会发生丢失:
1.隐式丢失
当进行隐式绑定时,如果进行一次引用赋值或者传参操作,会造成this的丢失,使this绑定到全局对象中去。
1.1引用赋值丢失
function thisTo(){
console.log(this.a);
}
var data={
a:2,
foo:thisTo //通过属性引用this所在函数
};
var a=3;//全局属性
var newData = data.foo; //这里进行了一次引用赋值
newData(); // 3
原理:因为newData实际上引用的是foo函数本身,这就相当于:var newData = thisTo;data对象只是一个中间桥梁,data.foo只起到传递函数的作用,所以newData跟data对象没有任何关系。而newData本身又不带a属性,最后a只能指向window。
1.2传参丢失
function thisTo(){
console.log(this.a);
}
var data={
a:2,
foo:thisTo //通过属性引用this所在函数
};
var a=3;//全局属性
setTimeout(data.foo,100);// 3
这就是本文开始的那个题目。所谓传参丢失,就是在将包含this的函数作为参数在函数中传递时,this指向改变。setTimeout函数的本来写法应该是setTimeout(function(){......},100);100ms后执行的函数都在“......”中,可以将要执行函数定义成var fun = function(){......},即:setTimeout(fun,100),100ms后就有:fun();所以此时此刻是data.foo作为一个参数,是这样的:setTimeout(thisTo,100);100ms过后执行thisTo(),实际道理还跟1.1差不多,没有调用thisTo的对象,this只能指向window。
1.3隐式丢失解决方法
为了解决隐式丢失(隐式丢失专用)的问题,ES5专门提供了bind方法,bind()会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。(这个bind可跟$(selector).bind('click',function(){......})的用法不同)
function thisTo(){
console.log(this.a);
}
var data={
a:2
};
var a=3;
var bar=thisTo.bind(data);
console.log(bar()); //2
2.间接引用间接引用是指一个定义对象的方法引用另一个对象存在的方法,这种情况下会使得this指向window:
function thisTo(){
console.log(this.a);
}
var data={
a:2,
foo:thisTo
};
var newData={
a:3
}
var a=4;
data.foo(); //2
(newData.foo=data.foo)() //4
newData.foo(); //3
这里为什么(newData.foo=data.foo)()的结果是4,与newData.foo()的结果不一样呢?按照正常逻辑的思路,应该是先对newData.foo赋值,再对其进行调用,也就是等价于这样的写法:newData.foo=data.foo;newData.foo();然而这两句的输出结果就是3,这说明两者不等价。
接着,当我们console.log(newData.foo=data.foo)的时候,发现打印的是thisTo这个函数,函数后立即执行括号将函数执行。这句话中,立即执行括号前的括号中的内容可单独看做一部本,该部分虽然完成了赋值操作,返回值却是一个函数,该函数没有确切的调用者,故而立即执行的时候,其调用对象不是newData,而是window。下一句的newData.foo()是在给newData添加了foo属性后,再对其调用foo(),注意这次的调用对象为newData,即我们上面说的隐式绑定的this,结果就为3。
3.ES6箭头函数ES6的箭头函数在this这块是一个特殊的改进,箭头函数使用了词法作用域取代了传统的this机制,所以箭头函数无法使用上面所说的这些this优先级的原则,注意的是在箭头函数中,是根据外层父亲作用域来决定this的指向问题。
function thisTo(){
setTimeout(function(){
console.log(this.a);
},100);
}
var obj={
a:2
}
var a=3;
thisTo.call(obj); //3
不用箭头函数,发生this传参丢失,最后的this默认绑定到全局作用域,输出3。
function thisTo(){
setTimeout(()=>{
console.log(this.a);
},100);
}
var obj={
a:2
}
var a=3;加粗文字
thisTo.call(obj); //2
用了箭头函数,不会发生隐式丢失,this绑定到外层父作用域thisTo(),thisTo的被调用者是obj对象,所以最后的this到obj对象中,输出2。
如果不用箭头函数实现相同的输出,可以采用下面这种方式:
function thisTo(){
var self=this; //在当前作用域中捕获this
setTimeout(function(){
console.log(self.a); //传入self代替之前的this
},100);
}
var obj={
a:2
}
var a=3;
thisTo.call(obj); //2