學(xué)會JS的this這一篇就夠了,根本不用記

以前看某本書上講:掌握了JS中this的用法才算真正的跨過了JS的門檻 我深以為是!?

但是

JS的this卻并不是那么簡單的內(nèi)容,許多同學(xué)都很困惑,也肯定有同學(xué)像我曾經(jīng)一樣查閱各種資料想知道How to understand this of javascript。很幸運(yùn)的是,網(wǎng)上有非常非常多的文章關(guān)于this的,簡直就是琳瑯滿目…

曾經(jīng),我看到某些文章非常開心,因?yàn)樗麄冎v得確實(shí)非常好,以至于我確信我已掌握了this的用法。然而可能是由于我太笨了,經(jīng)常過一段時(shí)間就忘得一干二凈了,這真的是一件很尷尬的事情……直到后來我仔細(xì)地琢磨又琢磨,終于感覺我可能以后不會再忘記了。

所以想把我琢磨的內(nèi)容和大家分享一下,說到this,就不得不提到function,相信看過其它類似文章的同學(xué)也知道,正是由于調(diào)用function的對象不同,才導(dǎo)致了this的指向不同。

所以以前老是去記憶每種調(diào)用function的情況所對應(yīng)的this,因?yàn)榍闆r有限而且很少,所以這當(dāng)然是可行的——對于聰明人來說。所以我不得不思考另外一些方式來讓我記住。

那么首先我們需要明確的一個(gè)事情是:

function也是對象

同時(shí)我們還需要明確的一個(gè)事情是:

function執(zhí)行時(shí)是在某個(gè)特定的上下文中執(zhí)行的。那什么是上下文呢?打個(gè)比方,比如你練會了辟邪劍譜,那這時(shí)候你的掌門讓你用辟邪劍譜砍人。

如果僅僅是這樣的話,你是沒法完成這個(gè)任務(wù)的,因?yàn)槟惚仨毜弥酪痴l吧,其次去哪兒砍吧,那么是個(gè)地下通道還是一望無盡的大草原,要是地下通道你走路都困難,還怎么用辟邪劍譜呢對吧。

這就是上下文,函數(shù)執(zhí)行時(shí)它也需要一些額外的信息來支撐它的運(yùn)行。那么既然function是對象的話,就會有方法。而function中最核心的方法是call方法。因此我們就從這兒入手。

call方法

先來看一下如何使用call方法:

functionsay(content){console.log("From?"+this+":?Hello?"+?content);?}

say.call("Bob","World");//==>?From?Bob:?Hello?World

接下來仔細(xì)分析一下call的用法:

Step1: 把第二個(gè)到最后一個(gè)參數(shù)作為函數(shù)執(zhí)行時(shí)要傳入的參數(shù)

Step2: 把函數(shù)執(zhí)行時(shí)的this指向第一個(gè)參數(shù)

Step3: 在上面這個(gè)特殊的上下文中執(zhí)行函數(shù)

上面例子中,我們通過call方法,讓say函數(shù)執(zhí)行時(shí)的this指向"Bob",然后把"World"作為參數(shù)傳進(jìn)去,所以輸出結(jié)果是可以預(yù)見的。

js執(zhí)行函數(shù)時(shí)會默認(rèn)完成以上的步驟,你可以把直接調(diào)用函數(shù)理解為一種語法糖

比如:

functionsay(word){console.log(world);?}

say("Hello?world");?say.call(window,"Hello?world");

以上可以把say("Hello world")看做是say.call(window,"Hello world")的語法糖。

這個(gè)結(jié)論非常關(guān)鍵 所以以后每次看見functionName(xxx)的時(shí)候,你需要馬上在腦海中把它替換為functionName.call(window,xxxx),這對你理解this的指向非常重要。

不過也有例外,在ES5的strict mode中call的第一個(gè)參數(shù)不是window而是undefined。之后的例子我假設(shè)總是不在strictmode下,但你需要記住strictmode有一點(diǎn)兒不同。

對于匿名函數(shù)來說,上面的結(jié)論也是成立的

(function(name){?//?})("aa");?//等價(jià)于?(function(name){?//?}).call(window,"aa");

函數(shù)作為對象的方法被調(diào)用

直接來看代碼:

varperson?=?{

name:"caibirdme",

run:function(time){

console.log(this.name?+"has?been?running?for?over?"+?time+"?minutes");

}?};

person.run(30);//==>?caibirdme?has?been?running?for?over?30?minutes?

//等價(jià)于?person.run.call(person,?30);

?//?the?same

你會發(fā)現(xiàn)這里call的第一個(gè)參數(shù)是person而不是window。

當(dāng)你明白了這兩點(diǎn),下意識地把函數(shù)調(diào)用翻譯成foo.call()的形式,明確call的第一個(gè)參數(shù),那基本上this的問題就難不住你了。

還是來舉幾個(gè)例子吧

例一:

functionhello(thing){

console.log(this?+"?says?hello?"+?thing);

}

person?=?{?name:"caibirdme"}

person.hello?=?hello;

person.hello("world")

//?相當(dāng)于執(zhí)行?person.hello.call(person,"world")

//caibirdme?says?hello?world?hello("world")

//?相當(dāng)于執(zhí)行?hello.call(window,"world")

//[object?DOMWindow]world

例二:

varobj?=?{

x:20,

f:function(){

console.log(this.x);

}?};

obj.f();//?obj.f.call(obj)?//==>?20

obj.innerobj?=?{

x:30,

f:function(){

console.log(this.x);

}?}

obj.innerobj.f();//?obj.innerobj.f.call(obj.innerobj)?//?==>?30

例三:

varx?=10;

varobj?=?{

x:20,f:function(){

console.log(this.x);//this?equals?obj?//?==>?20

varfoo?=function(){

console.log(this.x);

}

foo();//?foo.call(window)?//foo中this被指定為window,所以==>?10?}?};?obj.f();

?//?obj.f.call(obj)//?==>?20?10

由例三引出一個(gè)非常common的問題,如果我想讓foo輸出20怎么辦?這時(shí)候需要用到一點(diǎn)小技巧

例四:

varx?=10;

varobj?=?{

x:20,

f:function(){

console.log(this.x);

varthat?=this;//使用that保留當(dāng)前函數(shù)執(zhí)行上下文的this

varfoo?=function(){console.log(that.x);?}

//此時(shí)foo函數(shù)中的this仍然指向window,但我們使用that取得obj?foo();?

//?foo.call(window)?}?};

obj.f();

obj.f.call(obj)//?==>?20?20

再來一個(gè)稍微難一點(diǎn)點(diǎn)的(但其實(shí)用call替換法一點(diǎn)兒也不難)

例五:

varx?=10;

varobj?=?{

x:20,

f:function(){

console.log(this.x);

}?};

obj.f();//?obj.f.call(obj)?//?==>?20

varfOut?=?obj.f;

fOut();//?fOut.call(window)?//==>?10

varobj2?=?{x:30,f:?obj.f?}?obj2.f();//?obj2.f.call(obj2)?//==>?30

例五有些同學(xué)會可能出錯(cuò)的原因,是沒有明確我上面說的:

this是在執(zhí)行是才會被確認(rèn)的

他可能會認(rèn)為說obj.f那個(gè)函數(shù)定義在obj里面,那this就該指向obj。

如果看完這篇文章你還這么想的話,我會覺得我的表達(dá)水平太失敗了…… 用于構(gòu)造函數(shù) 先看一段代碼:

funcperson(name){?this.name?=?name;?}

varcaibirdme?=newperson("deen");

//?caibirdme.name?==?deen

我上面也說了,函數(shù)在用作構(gòu)造函數(shù)時(shí)同樣可以用call方法去代替,那這里怎么代替呢?

這里你又需要明確一點(diǎn):

newconstrcut()是一種創(chuàng)建對象的語法糖?它等價(jià)于

functionperson(name){this.name?=?name;?}

varfoo?=newperson("deen");//通過new創(chuàng)建了一個(gè)對象?//new是一種語法糖,

newperson等價(jià)于varbar?=?(function(name){var_newObj?=?{constructor:?person,__proto__:?person.prototype,?};

_newObj.constructor(name);//?_newObj.constructor.call(_newObj,?name)?return?_newObj;?})();

So you can see……為什么new的時(shí)候this就指向新的對象了吧?

通過我這篇文章,我希望學(xué)會通過把一個(gè)函數(shù)調(diào)用替換成funcName.call的形式,從而理解運(yùn)行時(shí)上下文中this到底指向誰。

總結(jié)來說就是下面兩個(gè)等價(jià)變形:

foo() ---> foo.call(window)

obj.foo() --> obj.foo.call(obj) 只要理解以上兩個(gè)變形,this就不再是問題啦??!

希望我的這種方法對各位同學(xué)認(rèn)識this有所幫助,不要再像我曾經(jīng)一樣掉入this的坑中,相關(guān)面試題也不再怕怕啦,哈哈。如果還有問題的話,歡迎下面留言。

如果您在學(xué)習(xí)編程的過程中遇到難題,歡迎關(guān)注微信公眾號【筑夢編程】,大家一起交流解決!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容