揭开this最深处的面纱

this是什么?

该怎样理解JS中的this?

勉强回答可以是:this有时候指的是全局作用域。有时候指的是对象本身。使用apply和call的时候this指向指定的上下文。

this有太多怪异的情况会导致程序员抓狂,很多时候很难搞清this指向,所以很多时候我们选用了self=this来防止this混乱。

《you don't know JS》一书中提到“软绑定”和“硬绑定”以及“丢失绑定”。除了这些,还有复杂引用中this的变化。本文也是在阅读该书的相关介绍后整理而成。

其实this并不神秘,主要有四个主体情形可以确认this的指向,另外再注意一些特殊情况就可以理解this了。

代码案例:

【代码-1】包含this指向的四种情况,初看会有不明白觉得重复的地方,没事。跳过,等通读全文后回过头来再看这里(建议直接跳过这段代码)。

function animal(newName){
    if(newName=='pard'){
        console.log(this.name);
        return;
    }
    this.name=newName;//this指向谁了?
}
var name='global animal';
var zoo={};
var zoo2={name:'zoo2 animal',getAnimal:animal};
var zoo3=zoo2.getAnimal;

zoo3('pard');//'global animal'
zoo2.getAnimal('monkey');
console.log(zoo2.name);//zoo2 monkey
zoo2.getAnimal.call(zoo,'lion');
console.log(zoo.name);//zoo lion
//第一段对比///
var forest1=new zoo2.getAnimal('horse');
console.log(zoo2.name,forest1.name);//zoo2 monkey     forest1 horse
//第二段对比///
zoo2.getAnimal.call(zoo,'thisCallAnimal');
console.log(zoo.name,zoo2.name);
//第三段对比///
var forest2=animal.bind(zoo);
forest2('panda');
console.log(zoo.name);//panda
var BigForest=new forest2('tiger');
console.log(zoo.name,BigForest.name);//panda   tiger

代码-1

this指向情形一:(即为默认情形,指向全局对象)

通过【代码-1】中关于情形一的截取:

function animal(newName){
    console.log(this.name);
}
var name='global animal';
var zoo2={
    name:'zoo2 animal',
    getAnimal:animal
};
animal('pard');//'global animal'
var zoo3=zoo2.getAnimal;
zoo3('pard');//'global animal'

代码-2

通过【代码-2】我们看到,有两个打印。

先看直接执行animal('pard')的时候:

this.name指向了全局对象中的name,打印出'global animal',也就是说此时的this绑定在了全局对象上。为何如此?因为我们在调用animal()时,并没有做任何的特殊处理(那什么是特殊处理?比如下面几种情况将要介绍到的call,apply,bind,softBind等),所以这里符合默认情形(说白了就是备胎,其他情形都不符合才会考虑默认),this就会指向全局对象。

再看zoo3('pard'):

zoo3=zoo2.getAnimal,而zoo2.getAnimal指向的就是animal方法。也就是zoo3其实就是animal方法,zoo3('pard')和animal('pard')也就呈现出相同的结果了。所以符合默认情形,this就会指向全局对象。

this指向情形二&三之对象绑定:此时this会指向被绑定的对象

普通绑定(情形二):

对情形一中【代码-2】稍加修改

function animal(newName){
    console.log(this.name);
}
var name='global animal';
var zoo2={
    name:'zoo2 animal',
    getAnimal:animal
};
zoo2.getAnimal('prad');//zoo2 animal
var zoo3=zoo2.getAnimal;
zoo3('pard');//'global animal'

代码-3

【代码-3】中zoo2完成了一次普通绑定,而zoo3在【代码-2】中已经提到了,出现在这里是为了跟此处zoo2做一个对比。

这里animal被zoo2的getAnimal属性引用了。我们在使用zoo2.getAnimal的时候,虽然执行了animal方法,但是该animal方法被调用的时候位置是在zoo2对象下的getAnimal,也就是,animal此时被调用的地方的上下文是zoo2而不是zoo2之外的全局对象。这就等于给animal绑定了上下文为zoo2,所以此时,this.name就成了zoo2.name。

这里通过引入了一个概念:方法被调用的地方的上下文可以绑定this,用这个概念我们来看作为对比的zoo3,情形一中提到zoo3=...,实际zoo3就是应用了animal,所以此时animal被调用的地方就是zoo3('..')这行代码出现的地方,这里上下文并没有明确,所以默认全局对象为上下文。这就是一开始说的“绑定丢失”(从zoo2丢失)

上述情形二一直都说的是普通绑定,那就还有非普通绑定,称为:显式绑定(明确通知想要哪个对象作为上下文!)。

显式绑定(情形三):

下面是对【代码-1】的提取

function animal(newName){
    this.name=newName;//this指向谁了?
}

var zoo={};
var zoo2={name:'zoo2',getAnimal:animal};

zoo2.getAnimal.call(zoo,'lion');
console.log(zoo.name,zoo2.name);//lion  zoo2

代码-4

call和apply可以实现对方法的继承,如:funa.call(obj)或者funa.apply(obj)都可以实现obj继承方法funa。同时,call和apply还会明确要求funa中this将指向obj,这种明确告知上下文的就是显示绑定。

来看【代码-4】:

zoo2.getAnimal.call(zoo,'lion')是让zoo继承了zoo2的getAnimal,同时传入执行参数'lion',因为该方法明确告知了this指向zoo,所以this.name=newName改变的就是zoo的name而非zoo2。通过输出结果就可以看出。

this指向情形四:this会指向new方法创造新的对象

下面是对【代码-1】的提取:

function animal(newName){
    this.name=newName;//this指向谁了?
}
var zoo2={name:'monkey',getAnimal:animal};
var forest1=new zoo2.getAnimal('horse');
console.log(zoo2.name,forest1.name);//zoo2 monkey     forest1 horse

代码-5

【代码-5】中new  zoo2.getAnimal('...')实际就是new  animal('...'),使用new创造了一个新的animal对象forest1,此时this指向了新的对象forest1。通过输出结果可以看出。

 

总结一:

至此,this的四种情况我们都了解清楚了。是否只是如此而已呢?如果一个地方四种情况同时出现,this岂不是手忙脚乱不知道到底该指向谁?其实不然。this是有自己的先后顺序的。

这个时候就是我们全面回顾【代码-1】的时候了。

注意【代码-1】中注释有“第一段对比”的代码书写方式:

var forest1=new zoo2.getAnimal('horse');

仔细看这行代码,有zoo2.getAnimal('horse')这个普通绑定的存在,也有forest1=new  ....的new“构造””(加引号是因为JS没有真正意义的面向对象的概念,自然没有真正意义的构造函数)。通过输出结果我们可以看到zoo2.name没有变化,而forest1.name赋值为'horse'。

说明同时存在普通绑定和new “构造时,new“构造”会起作用,普通绑定会失效。

再看【代码-1】中注释有“第二段对比”的代码书写方式:

zoo2.getAnimal.call(zoo,'thisCallAnimal');

同样发现这里面包含了两种可以确定this指向的类型:zoo2.getAnimal为代表的普通绑定上下文类型和call(obj)为代表的显式绑定上下文类型。通过输出结果可以看出‘thisCallAnimal’被赋给了zoo而非zoo2。

说明同时存在普通绑定和显式绑定时,显式绑定会起作用,普通绑定会失效。

猜测一个结论:“new”状元--->“显式绑定”榜眼--->“普通绑定”探花--->被作为候选项的“默认绑定”可能连秀才都不是吧

本着无愧每位的寒窗苦读的学子,皇帝要求再检查一下显式绑定和new的答卷到底更优。为啥不复查“默认绑定”?因为皇帝根本懒得理这么底层的学渣。

在阅读第三段对比之前还得补充一个知识,就是一开始说的硬绑定”---bind方法。至于硬绑定的目的以及相应拓展就不在这里介绍了。贴出其实现代码给众看官看一下就知道bind实际就类似apply:

function bind(fn,obj){
    return function(){fn.apply(obj,arguments)};
}

所以对比显式绑定和new就可以通过下面方法的实现:

且看【代码-1】中注释有“第三段对比”的代码书写方式:

var forest2=animal.bind(zoo);
forest2('panda');
var BigForest=new forest2('tiger');

可以看到forest2是一个显式绑定,而bigFprest=new...是一个new“构造”,通过最终结果可以看出“tiger”给了bigForest而非forest2。

于是皇帝正式下发皇榜:新科状元:new!,殿试情况如下:

“new”状元--->“显式绑定”榜眼--->“普通绑定”探花       当然别忘了还有个备胎“默认绑定”。

 

至此,this的介绍就只缺少“软绑定”了。

软绑定介绍:

为何要有软绑定?解决啥问题了?

回答这个问题之前我们还是先简答介绍下硬绑定的作用和弊端吧。作用:硬绑定可以防止我们使用上下文绑定(情形二&情形三)时出现情形二中介绍的绑定丢失,硬绑定就是强制绑定不讲道理和原则。缺点:太生硬了,无法变通。比如,我们想通过显式绑定或者普通绑定来改变this,在使用硬绑定的情况下:sorry!no way!好不友好的情况。这时候就需要软绑定。

软绑定同样可以防止绑定丢失,但他的方法不是使用硬绑定,而是判断当前绑定有没有消失,如果消失了就回到指定的地方。翻译成人话就是:检查this的绑定,如果this绑定的是全局,说明this丢失了,此时就绑定一个指定的上下文给this,如果this不是指向全局,那就不做任何改变了。这样无论我们怎么使用call或者apply或者普通绑定,都不会被制止。

 

终于写完了。this终于介绍完了。终于可以休息了。介绍完了?当然没有!继续奋斗。特殊的用法需要介绍下。

情形一:

有个班级=[a,b,c,d,...],有个点名方法(calltheroll),我想给[a,b,c,d...]点一下名,需要班级继承点名方法。此时我们使用call或者apply的时候是这样用:calltheroll.apply(obj,[...]);obj哪来的?我根本不需要啊,这不是浪费我的存储么。果断改为calltheroll.apply(null,[...]);这时候后this就不知所指了,总不能绑定一个Null吧?此时this会指向全局。当然,此时如果有this的操作,自然也就污染全局了,所以还是把obj放进去吧,不过可以把obj设置为空对象。

情形二:

ES6中的箭头函数。

function(){

   //scope1 this

   ()=>{

      //scope2 this

   }

}

scope2中的this指向就是scope1中的this。=>有个特殊功能,定义的方法this指向相对其外部不会改变。这在ES5中可以这么理解:

function(){

   var self=this;

  function(){

      //self

   }

}

 才疏学浅,知之如此。

   2017/03/14

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值