以前看某本書上講:掌握了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)注微信公眾號【筑夢編程】,大家一起交流解決!