在翻閱《你不知道的javascript》這一套書的中上卷目錄之后定躏,發(fā)現(xiàn)書中針對(duì)閉包诸迟、對(duì)象、原型受裹、語法斗这、異步动猬、回調(diào)等等既基礎(chǔ)又重要的
javascript知識(shí)有著針對(duì)性的闡述,于是決定對(duì)這套書的中上卷進(jìn)行學(xué)習(xí)涝影。上卷和中卷各講述了兩大部分知識(shí)枣察,分別是:作用域與閉包、
this和對(duì)象原型燃逻、類型和語法序目、異步和性能。本文是對(duì)作用域與閉包的學(xué)習(xí)總結(jié)伯襟。
對(duì)于作用域及其相關(guān)知識(shí)的理解猿涨,我認(rèn)為主要把握這樣一些東西:js所使用作用域的類型,IIFE以及作用域中的提升姆怪。
1.作用域類型
作用域分為詞法作用域和動(dòng)態(tài)作用域叛赚,js使用的是詞法作用域。所謂詞法作用域稽揭,指的是在編譯詞法分析階段根據(jù)詞法單元確定下來的作用域俺附,并且在引擎執(zhí)行代碼階段作用域是不變的(大部分情況下),注意括號(hào)里的大部分情況溪掀,因?yàn)樵谕ǔ?duì)語法的學(xué)習(xí)中事镣,只要注意一些不符合常規(guī)情況的狀態(tài),對(duì)語法的把握會(huì)順利許多揪胃。
一小部分會(huì)在執(zhí)行代碼期間改變作用域的情況(書中稱之為“欺騙”)是兩種:在js中使用了eval()和with()方法(在非嚴(yán)格模式下):
- eval()可以接受一個(gè)字符串作為參數(shù)璃哟,并且在執(zhí)行代碼期間氛琢,將參數(shù)中可能傳遞的值看作本來就存在于eval()所在位置的代碼;
- with()主要作用是可以接受一個(gè)對(duì)象随闪,并快捷引用其中的屬性阳似。然而,with()在處理這個(gè)對(duì)象的時(shí)候铐伴,不僅會(huì)形成一個(gè)新的作用域撮奏,
還可能改變?cè)敬嬖诘脑~法作用域(這個(gè)方法比較復(fù)雜)。
對(duì)eval()舉個(gè)例子:
function foo(str,a) {
eval(str);
console.log(a,b);
}
var b = 2;
foo("var b = 3",1);//1,3
在執(zhí)行foo("var b = 3",1)語句時(shí)当宴,在正常情況下挽荡,函數(shù)foo(str,a)執(zhí)行到console.log(a,b)時(shí),查找b會(huì)找到全局變量中的b并得到2即供,但實(shí)際卻得到3,這是因?yàn)閑val()將“var b = 3”看作在函數(shù)foo(str,a)中的代碼于微,相當(dāng)于在函數(shù)中聲明了一個(gè)b作為局部變量逗嫡,于是下一句console.log(a,b)執(zhí)行的時(shí)候先找到局部變量得到值3,就結(jié)束了查找,而全局變量b被屏蔽了株依。
在嚴(yán)格模式下驱证,eval()有自己的作用域,不會(huì)在其所處函數(shù)中產(chǎn)生屏蔽效應(yīng)恋腕,而with()是被禁用的抹锄。并且,在第一章中講到荠藤,引擎在優(yōu)化性能的時(shí)候伙单,是根據(jù)已經(jīng)確定的作用域和詞法單元來進(jìn)行優(yōu)化的,在使用了eval()和with()后哈肖,使得函數(shù)作用域可能在動(dòng)態(tài)情況下(執(zhí)行代碼期間)發(fā)生變化吻育,而引擎在遇到這個(gè)兩個(gè)方法后就不會(huì)進(jìn)行性能優(yōu)化。所以淤井,在js中使用這兩個(gè)方法會(huì)導(dǎo)致性能下降布疼。
對(duì)于詞法作用域,其中又分成了塊作用域和函數(shù)作用域币狠,js絕大部分情況下使用的是函數(shù)作用域游两,但存在個(gè)別塊作用域的時(shí)候,使用with()和try~catch語句的時(shí)候會(huì)形成塊作用域(又是with()漩绵,在這里贱案,《高程》第三版第4章中也講到了with()會(huì)形成作用域,但《高程》中提到這種情況使得作用域鏈變長了渐行,而with()形成的依然是函數(shù)作用域)轰坊,并且ES6出來之后铸董,也正式承認(rèn)了塊作用域的存在和使用,在{}中使用let和const聲明就能創(chuàng)造塊作用域了肴沫,這為使用循環(huán)語句等提供了很大的便利粟害。
2.IIFE
IIFE是立即執(zhí)行函數(shù)表達(dá)式。首先應(yīng)該理解什么是函數(shù)表達(dá)式颤芬,非常簡單悲幅,以function單詞作為開頭的是函數(shù)聲明,而帶有function但并非以其開頭的語句就是函數(shù)表達(dá)式站蝠,最典型的情況就是(function(){})汰具。函數(shù)聲明和函數(shù)表達(dá)式相同的地方在于形成了函數(shù)作用域,而不同的地方函數(shù)聲明會(huì)被提升菱魔,而表達(dá)式不會(huì)(顯然不會(huì)留荔,因?yàn)槁暶鞑攀蔷幾g器工作的對(duì)象)。
函數(shù)表達(dá)式的一個(gè)主要作用是可以使函數(shù)匿名澜倦,這樣就避免了函數(shù)作用域中多出一個(gè)標(biāo)識(shí)符聚蝶。但是并非所有情況下匿名都是最優(yōu)的,例如:當(dāng)函數(shù)不只需要被調(diào)用一次的時(shí)候藻治,就不能匿名了碘勉。
而函數(shù)表達(dá)式另一個(gè)主要的作用就是作為IIFE的出現(xiàn),既(function(){})()或(function(){}())的形式桩卵,可以立即執(zhí)行函數(shù)验靡,而無需調(diào)用或加載。而IIFE多出來的括號(hào)并不是只使得函數(shù)會(huì)立即執(zhí)行雏节,在括號(hào)中還可以傳遞需要的參數(shù)胜嗓。例如,以(function(a,b){})(c,d)的形式既能將參數(shù)傳遞進(jìn)函數(shù)了矾屯,而簡單的函數(shù)表達(dá)式(function(){})卻沒有這樣的功能兼蕊。
此外,IIFE還是閉包機(jī)制的一個(gè)最佳實(shí)踐(雖然不是最能體現(xiàn)閉包機(jī)制的方式)件蚕,在第三部分的閉包中會(huì)講到孙技。
3.作用域中的提升
其實(shí)作用域中的提升非常簡單,只要能夠理解js的編譯原理:在編譯器工作階段排作,會(huì)進(jìn)行聲明牵啦,以及對(duì)其他語句形成引擎執(zhí)行的代碼,因此妄痪,在編寫的代碼中哈雏,不管一個(gè)作用域中的聲明處于哪里,肯定都比賦值等其他句語先完成,而js語句的執(zhí)行是按照從上到下執(zhí)行的裳瘪,這就好比將聲明從下統(tǒng)一提升到了函數(shù)中最上面的地方土浸,這就稱為聲明。
對(duì)提升需要把握的要點(diǎn)有以下三處:
- 函數(shù)表達(dá)式不會(huì)提升彭羹;
- let和const聲明不會(huì)提升黄伊;
- 函數(shù)聲明會(huì)提升到變量聲明的上方,而在同一個(gè)作用域中聲明了同名函數(shù)時(shí)派殷,后一個(gè)同名函數(shù)在提升時(shí)會(huì)覆蓋前一個(gè)函數(shù)的聲明还最。