js中this的绑定与丢失

一直以来,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

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值