聊聊JavaScript 編譯器,引擎,作用域

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)注明出處

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

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

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 3,324評(píng)論 5 34
  • You don't KnowJS 引語(yǔ):你不懂的JS這本書(shū)?github上已經(jīng)有了7w的star最近也是張野大大給...
    Sleet閱讀 666評(píng)論 0 0
  • 如果我的文章對(duì)你有用,請(qǐng)給我一個(gè)贊,讓我有繼續(xù)堅(jiān)持的動(dòng)力/微笑。原創(chuàng)文章,此文章僅供學(xué)習(xí)參考使用,歡迎訪問(wèn)我的個(gè)人...
    我就是z閱讀 543評(píng)論 0 3
  • 程序需要存儲(chǔ)變量中的值,并且能在之后對(duì)這個(gè)值進(jìn)行訪問(wèn)或修改。這些變量存儲(chǔ)在哪里?程序如何找到他們?這些問(wèn)題需要一套...
    zyanfly閱讀 348評(píng)論 0 1
  • 代碼解析參與者 需要了解變量是如何進(jìn)行預(yù)解析的,首先要知道解析代碼的參與者,有三個(gè):引擎、編譯器、作用域 編譯器對(duì)...
    素彌閱讀 642評(píng)論 0 1

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