首先對this的下個(gè)定義:this是在執(zhí)行上下文創(chuàng)建時(shí)確定的一個(gè)在執(zhí)行過程中不可更改的變量疼鸟。
this只在函數(shù)調(diào)用階段確定,也就是執(zhí)行上下文創(chuàng)建的階段進(jìn)行賦值柏蘑,保存在變量對象中幸冻。這個(gè)特性也導(dǎo)致了this的多變性:即當(dāng)函數(shù)在不同的調(diào)用方式下都可能會(huì)導(dǎo)致this的值不同:
vara =1;functionfun(){? 'use strict';vara =2;returnthis.a;}fun();//報(bào)錯(cuò) Cannot read property 'a' of undefined
嚴(yán)格模式下,this指向undefined;??
vara =1;functionfun(){vara =2;returnthis.a;}fun();//1
即在不同模式下之所以有不同表現(xiàn)咳焚,就是因?yàn)閠his在嚴(yán)格模式洽损,非嚴(yán)格模式下的不同。??
結(jié)論:當(dāng)函數(shù)獨(dú)立調(diào)用的時(shí)候革半,在嚴(yán)格模式下它的this指向undefined碑定,在非嚴(yán)格模式下,當(dāng)this指向undefined的時(shí)候又官,自動(dòng)指向全局對象(瀏覽器中就是window)
在全局環(huán)境下延刘,this就是指向自己:
this.a =1;varb =1;c =1;console.log(this===window)//true//這三種都能得到想要的結(jié)果,全局上下文的變量對象中存在這三個(gè)變量
當(dāng)this不在函數(shù)中用的時(shí)候六敬,this還是指向了window:
vara =100;varobj = {a:1,b:this.a +1}functionfun(){varobj = {a:1,c:this.a +2//嚴(yán)格模式下這塊報(bào)錯(cuò) Cannot read property 'a' of undefined}returnobj.c;}console.log(fun());//102console.log(obj.b);//101
結(jié)論:當(dāng)obj在全局聲明的時(shí)候碘赖,obj內(nèi)部屬性中的this指向全局對象,當(dāng)obj在一個(gè)函數(shù)中聲明的時(shí)候,嚴(yán)格模式下this會(huì)指向undefined崖疤,非嚴(yán)格模式自動(dòng)轉(zhuǎn)為指向全局對象秘车。
現(xiàn)在知道了嚴(yán)格模式和非嚴(yán)格模式下this的區(qū)別,然而我們?nèi)粘?yīng)用最多的還是在函數(shù)中用this劫哼,上面也說過了this在函數(shù)的不同調(diào)用方式還有區(qū)別叮趴,那么函數(shù)的調(diào)用方式都有哪些呢?四種:
在全局環(huán)境或是普通函數(shù)中直接調(diào)用
作為對象的方法
使用apply和call
作為構(gòu)造函數(shù)
下面分別就四種情況展開:
直接調(diào)用
fun函數(shù)雖然在obj.b方法中定義权烧,但它還是一個(gè)普通函數(shù)眯亦,直接調(diào)用在非嚴(yán)格模式下指向undefined,又自動(dòng)指向了全局對象般码,正如預(yù)料妻率,嚴(yán)格模式會(huì)報(bào)錯(cuò)undefined.a不成立,a未定義板祝。
vara =1;varobj? =? {a:2,b:function(){functionfun(){returnthis.a? ? ? ? }console.log(fun());? ? }} obj.b();//1
作為對象的方法
b所引用的匿名函數(shù)作為obj的一個(gè)方法調(diào)用宫静,這時(shí)候this指向調(diào)用它的對象。這里也就是obj:
vara =1;varobj = {a:2,b:function(){returnthis.a;? }}console.log(obj.b())//2
那么如果b方法不作為對象方法調(diào)用:
vara =1;varobj = {a:2,b:function(){returnthis.a;? }}vart = obj.b;console.log(t());//1
如上券时,t函數(shù)執(zhí)行結(jié)果竟然是全局變量1孤里,為啥呢?這就涉及Javascript的內(nèi)存空間了橘洞,就是說捌袜,obj對象的b屬性存儲(chǔ)的是對該匿名函數(shù)的一個(gè)引用,可以理解為一個(gè)指針炸枣。當(dāng)賦值給t的時(shí)候虏等,并沒有單獨(dú)開辟內(nèi)存空間存儲(chǔ)新的函數(shù),而是讓t存儲(chǔ)了一個(gè)指針适肠,該指針指向這個(gè)函數(shù)霍衫。相當(dāng)于執(zhí)行了這么一段偽代碼:
vara =1;functionfun(){//此函數(shù)存儲(chǔ)在堆中returnthis.a;}varobj = {a:2,b: fun//b指向fun函數(shù)}vart = fun;//變量t指向fun函數(shù)console.log(t());//1
此時(shí)的t就是一個(gè)指向fun函數(shù)的指針,調(diào)用t迂猴,相當(dāng)于直接調(diào)用fun慕淡,套用以上規(guī)則,打印出來1自然很好理解了沸毁。
使用apply,call
這是個(gè)萬能公式,實(shí)際上上面直接調(diào)用的代碼傻寂,我們可以看成這樣的:
functionfun(){returnthis.a;}fun();//1//嚴(yán)格模式fun.call(undefined)//非嚴(yán)格模式fun.call(window)
這時(shí)候我們就可以解釋下息尺,為啥說在非嚴(yán)格模式下,當(dāng)函數(shù)this指向undefined的時(shí)候疾掰,會(huì)自動(dòng)指向全局對象搂誉,如上,在非嚴(yán)格模式下静檬,當(dāng)調(diào)用fun.call(undefined)的時(shí)候打印出來的依舊是1炭懊,就是最好的證據(jù)并级。
為啥說是萬能公式呢?再看函數(shù)作為對象的方法調(diào)用:
vara =1;varobj = {a:2,b:function(){returnthis.a;? }}obj.b()obj.b.call(obj)
如上侮腹,是不是很強(qiáng)大嘲碧,可以理解為其它兩種都是這個(gè)方法的語法糖罷了,那么apply和call是不是真的萬能的呢父阻?并不是愈涩,ES6的箭頭函數(shù)就是特例,因?yàn)榧^函數(shù)的this不是在調(diào)用時(shí)候確定的加矛,這也就是為啥說箭頭函數(shù)好用的原因之一履婉,因?yàn)樗膖his固定不會(huì)變來變?nèi)サ牧恕jP(guān)于箭頭函數(shù)的this我們稍后再說斟览。
作為構(gòu)造函數(shù)
何為構(gòu)造函數(shù)毁腿?所謂構(gòu)造函數(shù)就是用來new對象的函數(shù),像Function苛茂、Object已烤、Array、Date等都是全局定義的構(gòu)造函數(shù)味悄。其實(shí)每一個(gè)函數(shù)都可以new對象草戈,那些批量生產(chǎn)我們需要的對象的函數(shù)就叫它構(gòu)造函數(shù)罷了。注意侍瑟,構(gòu)造函數(shù)首字母記得大寫唐片。
functionFun(){this.name ='Lili';this.age =21;this.sex ='woman';this.run =function(){returnthis.name +'正在跑步';? }}Fun.prototype = {contructor: Fun,say:function(){returnthis.name +'正在說話';? }}varf =newFun();f.run();//Lili正在跑步f.say();//Lili正在說話
如上,如果函數(shù)作為構(gòu)造函數(shù)用涨颜,那么其中的this就代表它即將new出來的對象费韭。為啥呢?new做了啥呢庭瑰?
偽代碼如下:
functionFun(){//new做的事情varobj = {};? obj.__proto__ = Fun.prototype;//Base為構(gòu)造函數(shù)obj.name ='Lili';? ...//一系列賦值以及更多的事returnobj}
也就是說new做了下面這些事:
創(chuàng)建一個(gè)臨時(shí)對象
給臨時(shí)對象綁定原型
給臨時(shí)對象對應(yīng)屬性賦值
將臨時(shí)對象return
也就是說new其實(shí)就是個(gè)語法糖星持,this之所以指向臨時(shí)對象還是沒逃脫上面說的幾種情況。
當(dāng)然如果直接調(diào)用Fun(),如下:
functionFun(){this.name ='Damonre';this.age =21;this.sex ='man';this.run =function(){returnthis.name +'正在跑步';? }}Fun();console.log(window)
其實(shí)就是直接調(diào)用一個(gè)函數(shù)弹灭,this在非嚴(yán)格模式下指向window督暂,你可以在window對象找到所有的變量。
另外還有一點(diǎn)穷吮,prototype對象的方法的this指向?qū)嵗龑ο舐呶蹋驗(yàn)閷?shí)例對象的proto已經(jīng)指向了原型函數(shù)的prototype。這就涉及原型鏈的知識(shí)了捡鱼,即方法會(huì)沿著對象的原型鏈進(jìn)行查找八回。
箭頭函數(shù)
剛剛提到了箭頭函數(shù)是一個(gè)不可以用call和apply改變this的典型。
我們看下面這個(gè)例子:
vara =1;varobj = {a:2};varfun =()=>console.log(this.a);fun();//1fun.call(obj)//1
以上,兩次調(diào)用都是1缠诅。
那么箭頭函數(shù)的this是怎么確定的呢溶浴?箭頭函數(shù)會(huì)捕獲其所在上下文的 this 值,作為自己的 this 值管引,也就是說箭頭函數(shù)的this在詞法層面就完成了綁定士败。apply,call方法只是傳入?yún)?shù)汉匙,卻改不了this拱烁。
vara =1;varobj = {a:2};functionfun(){vara =3;letf =()=>console.log(this.a);? ? ? f();};fun();//1fun.call(obj);//2
如上,fun直接調(diào)用噩翠,fun的上下文中的this值為window戏自,注意,這個(gè)地方有點(diǎn)繞伤锚。fun的上下文就是此箭頭函數(shù)所在的上下文擅笔,因此此時(shí)f的this為fun的this也就是window。當(dāng)fun.call(obj)再次調(diào)用的時(shí)候屯援,新的上下文創(chuàng)建猛们,fun此時(shí)的this為obj,也就是箭頭函數(shù)的this值狞洋。
再來一個(gè)例子:
functionFun(){this.name ='Damonare';}Fun.prototype.say =()=>{console.log(this);}varf =newFun();f.say();//window
有的同學(xué)看到這個(gè)例子會(huì)很懵弯淘,感覺上this應(yīng)該指向f這個(gè)實(shí)例對象啊。不是的吉懊,此時(shí)的箭頭函數(shù)所在的上下文是proto所在的上下文也就是Object函數(shù)的上下文庐橙,而Object的this值就是全局對象。
那么再來一個(gè)例子:
functionFun(){this.name ='Damonare';this.say =()=>{console.log(this);? ? }}varf =newFun();f.say();//Fun的實(shí)例對象
如上借嗽,this.say所在的上下文态鳖,此時(shí)箭頭函數(shù)所在的上下文就變成了Fun的上下文環(huán)境,而因?yàn)樯厦嬲f過當(dāng)函數(shù)作為構(gòu)造函數(shù)調(diào)用的時(shí)候(也就是new的作用)上下文環(huán)境的this指向?qū)嵗龑ο蟆?/p>
作者:未來的IT女神
鏈接:http://www.reibang.com/p/77cff23936b4
來源:簡書
簡書著作權(quán)歸作者所有恶导,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處浆竭。