1 從語(yǔ)言說(shuō)起
提起JavaScript ,大家第一反應(yīng):腳本語(yǔ)言、解釋性執(zhí)行等,和java C這種編譯性語(yǔ)言搭不上邊。然而,事實(shí)上它確實(shí)是一門(mén)編譯語(yǔ)言。只是區(qū)別在于JS并不會(huì)像其他的編譯語(yǔ)言一樣進(jìn)行提前編譯,他的編譯過(guò)程(通常)是在實(shí)際執(zhí)行前進(jìn)行的,而且也不會(huì)產(chǎn)生可移植的編譯結(jié)果。
通常的編譯過(guò)程,會(huì)做以下幾個(gè)步驟:首先是分詞與詞法分析,把輸入的字符串分解為一些對(duì)編程語(yǔ)言有意義的代碼塊(詞法單元)。第二步解析與語(yǔ)法分析,這一步的操作高級(jí)了許多,會(huì)將上一步的詞法單元集合分析并最終轉(zhuǎn)換為一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語(yǔ)法結(jié)構(gòu)的樹(shù),稱(chēng)為抽象語(yǔ)法樹(shù)(Abstract Syntax Tree,AST)。第三步代碼生成就是將上一步的AST轉(zhuǎn)換為可執(zhí)行代碼。JavaScript引擎中的編譯器做的事情與這個(gè)類(lèi)似,但是因?yàn)镴S引擎的編譯過(guò)程就在代碼執(zhí)行前,對(duì)于“用戶”來(lái)說(shuō)是完全透明的。并且無(wú)法事先執(zhí)行編譯生成靜態(tài)文件,因此JS的編譯執(zhí)行效率就要比一般靜態(tài)語(yǔ)言敏感的多,故而也非常復(fù)雜。JS引擎在這一部分做了非常多的優(yōu)化,一是針對(duì)語(yǔ)法分析和代碼生成階段進(jìn)行優(yōu)化(例如針對(duì)冗余元素進(jìn)行優(yōu)化等),目的是提高編譯后的執(zhí)行效率。二是針對(duì)編譯過(guò)程進(jìn)行優(yōu)化(如JIT,延遲編譯甚至重編譯),目的是縮短編譯過(guò)程,保證性能最佳。
2 聊聊這三兄弟
- 引擎: 負(fù)責(zé)整個(gè)JS程序的編譯及執(zhí)行過(guò)程
- 編譯器: 負(fù)責(zé)語(yǔ)法分析及代碼生成等工作
- 作用域: 收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢(xún),實(shí)施一套非常嚴(yán)格的規(guī)則, 確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符的訪問(wèn)權(quán)限
說(shuō)白了,作用域是什么?是一個(gè)變量的“管家”,用一個(gè)事先定義好的規(guī)則(詞法作用域),管理變量的查詢(xún)與訪問(wèn)。
三兄弟合作:第一版
下面我們以一個(gè)最簡(jiǎn)單的例子var a = 2來(lái)進(jìn)行分析:
- 1 編譯器出馬,先進(jìn)行詞法分析,將該賦值操作拆分:
var a;a=2;。第一步var a,編譯器可以處理,他會(huì)先詢(xún)問(wèn)變量管家:作用域,是否存在一個(gè)該名稱(chēng)的變量?若存在,繼續(xù)編譯;若不存在,通知作用域聲明一個(gè)新變量,命名為a。 - 2 編譯器繼續(xù)為引擎進(jìn)行代碼生成,這些代碼主要用來(lái)處理
a=2這個(gè)賦值操作。 - 3 引擎拿到可執(zhí)行代碼,然后詢(xún)問(wèn)作用域:當(dāng)前有沒(méi)有一個(gè)叫a的變量啊? 如果有:使用這個(gè)變量,賦值給他;如果沒(méi)有就繼續(xù)往上級(jí)作用域查找,如果到根作用域仍然找不到,引擎直接報(bào)錯(cuò)拋異常,老子不干了,玩我呢╭(╯^╰)╮
這兒引入個(gè)關(guān)于變量查找的概念:
- LHS:賦值操作的左側(cè),試圖查找到變量的容器本身,從而可以對(duì)其賦值,即找到復(fù)制操作的目標(biāo)。
- RHS:另外一種查找,可以簡(jiǎn)單理解為復(fù)制操作的右側(cè),其查找目標(biāo)為取到目標(biāo)的源值,即找到這個(gè)變量具體的值而非容器。
舉個(gè)例子:
var a;//LHS 尋找a,未找到,通知作用域聲明一個(gè)新變量,命名為a
a=2;//LHS 找到a并給其賦值2
console.log(a);//RHS找到a的值2,并將其輸出
三兄弟合作:第二版
有了上面的基礎(chǔ)知識(shí),我們把三兄弟的合作再細(xì)化一下,例子也升級(jí)一下,用上面賦值并輸出的例子。
- 1 編譯器:作用域,我需要對(duì)a進(jìn)行LHS查找,你見(jiàn)過(guò)么?
- 2 作用域:我這找到根都沒(méi)看到啊,要不咱聲明一個(gè)吧!
- 3 編譯器:好,建好了,那我生成代碼了,引擎,給你你要的代碼。
- 4 引擎:收到,咦,需要一個(gè)a啊,作用域,幫我LHS找一下有沒(méi)有?
- 5 作用域: 找到了,編譯器已經(jīng)幫忙聲明了。
- 6 引擎:好的,那我對(duì)它賦值。
- 7 引擎:作用域,不要意思,我碰到一個(gè)console,需要RHS引用
- 8 作用域: 找到了,是個(gè)內(nèi)置對(duì)象,拿走不謝。
- 9 引擎: 好的作用域,對(duì)了能在幫我確認(rèn)一下a的RHS么?
- 10 作用域:確認(rèn)好了,沒(méi)變,拿去用吧,他的值是2
- 11 引擎:好咧,我把2傳遞給log(..)
疑問(wèn):為什么要這么啰嗦的區(qū)分LHS和RHS?其實(shí)細(xì)心的話,你應(yīng)該已經(jīng)發(fā)現(xiàn)了,這兩種查找有一個(gè)很重要的區(qū)別,即在變量未找到的時(shí)候的行為不同:
- RHS未找到:引擎會(huì)拋出錯(cuò)誤RefrenceError
- LHS未找到:引擎(或引擎中的編譯器)會(huì)幫你在頂層作用域聲明一個(gè)具有該名稱(chēng)的變量。(嚴(yán)格模式除外)
相比講到這兒,你應(yīng)該對(duì)這三兄弟的合作有了一個(gè)比較清楚的了解了吧?微笑臉 :-D
3 one more thing...
- 詞法作用域 :介紹作用域時(shí),我們有講過(guò)其根據(jù)一套規(guī)則來(lái)管理變量的查找與引用,詞法作用域就是js使用的規(guī)則,在編譯器進(jìn)行詞法化時(shí),其會(huì)根據(jù)你寫(xiě)代碼時(shí)將變量和塊作用域?qū)懺谀睦?,?lái)決定規(guī)則的內(nèi)容。這其中又包含了塊作用域這個(gè)概念,不展開(kāi)講,只要記住ES6之前沒(méi)有塊作用域,只有函數(shù)作用于,即:函數(shù)內(nèi)部是一個(gè)獨(dú)立的塊作用域。(有個(gè)特例:catch語(yǔ)句塊內(nèi)也是獨(dú)立的作用域)
- 變量提升: 明白了編譯器和引擎執(zhí)行之間的分工,其實(shí)你應(yīng)該就不會(huì)覺(jué)得變量提升是如此之詭異了,因?yàn)橐婺玫酱a的時(shí)候,編譯器已經(jīng)做了一些轉(zhuǎn)換(引擎旁白:這尼瑪真怪不得我啊/(ㄒoㄒ)/~)。編譯器干嘛要干這個(gè)事情?因?yàn)樗诘谝徊骄驼业剿械穆暶?,并且用合適的作用域?qū)⑺麄冴P(guān)聯(lián)起來(lái),這也正是詞法作用域的核心。表現(xiàn)為: 包括變量和函數(shù)在內(nèi)的所有聲明都會(huì)在當(dāng)前塊作用域內(nèi)被首先處理,即類(lèi)似于提升到最前面聲明,但是復(fù)制處理操作因?yàn)槭窃趫?zhí)行階段,因此編譯階段他們?cè)卮却龍?zhí)行 。不如留兩個(gè)練習(xí)?
//分析下面代碼的《三兄弟合作流程》并給出輸出
//第一個(gè)練習(xí):
a=2
var a;
console.log(a);
//第二個(gè)練習(xí):
console.log(a);
var a = 2;
了解不深,可能諸多紕漏,歡迎留言討論 :-)
原文鏈接:http://www.itdecent.cn/p/36f5bfc6b7e6
作者: changchao 轉(zhuǎn)載請(qǐng)注明出處