摘要:的抽象語法樹中可能如下圖所示代碼生成將轉(zhuǎn)換為可執(zhí)行代碼的過程被稱為代碼生成。如果是,編譯器會忽略該聲明,繼續(xù)進(jìn)行編譯,否則它會要求在當(dāng)前作用域的集合中聲明一個(gè)新的變量,并命名為。
在學(xué)習(xí) javascript 的過程中,我們第一步最應(yīng)該了解和掌握的就是作用域,與之相關(guān)還有程序是怎么編譯的,變量是怎么查找的,js 引擎是什么,引擎和作用域的關(guān)系又是什么,這些是 javascript 這門語言最基礎(chǔ)的地基,至于對象、函數(shù)、閉包、原型鏈、作用域鏈以及設(shè)計(jì)模式等等都是地基以上的建筑,只有地基打牢了,建筑才會穩(wěn)。同樣只有先把最基礎(chǔ)的部分掌握了,之后的擴(kuò)展學(xué)習(xí)才會更容易。
這一節(jié)我要說的,就是作用域和編譯原理,從這里開始,我會一點(diǎn)點(diǎn)的把深入學(xué)習(xí) javascript 的過程中總結(jié)的知識點(diǎn)以及遇到的問題,一篇一篇的梳理出來,如果有志同道合的朋友,可以關(guān)注我這個(gè)系列,我們一起玩轉(zhuǎn) javascript。
1. 編譯原理大家通常把 javascript 歸類為一種“動態(tài)”或“解釋執(zhí)行”的語言,但事實(shí)上,它是一門編譯語言,但和傳統(tǒng)的編譯語言不同,它不是提前編譯的,編譯結(jié)果也不能進(jìn)行移植。
在傳統(tǒng)編譯語言中,程序在執(zhí)行之前會經(jīng)歷三個(gè)步驟,統(tǒng)稱為“編譯”:
分詞/詞法分析
這個(gè)過程會把字符串分解成有意義的代碼塊,這些代碼塊被稱為詞法單元。
例如 var a = 5; 這段程序通常會被分解成下面這些詞法單元: var、a、=、5、; 。空格是否會被當(dāng)成詞法單元取決于空格在這門語言中是否有意義。
解析/語法分析
這個(gè)過程是將詞法單元流(數(shù)組)轉(zhuǎn)換成一個(gè)由元素逐級嵌套所組成的代表了程序語法結(jié)構(gòu)的樹。這個(gè)樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)。
var a = 5; 的抽象語法樹中可能如下圖所示:
代碼生成
將 AST 轉(zhuǎn)換為可執(zhí)行代碼的過程被稱為代碼生成。這個(gè)過程與語言、目標(biāo)平臺等息息相關(guān)。簡單來說,就是通過某種方法可以將 var a = 5; 的 AST 轉(zhuǎn)化為一組機(jī)器指令,用來創(chuàng)建一個(gè)叫做 a 的變量(包括分配內(nèi)存等),并將一個(gè)值 5 存儲在 a 中。
比起那些編譯過程只有三個(gè)步驟的語言的編譯器來說,javascript 引擎要復(fù)雜的多。
例如,在詞法分析和代碼生成階段有特定的步驟來對運(yùn)行性能進(jìn)行優(yōu)化,包括對冗余元素進(jìn)行優(yōu)化等。
首先我們要清楚,javaScript 引擎不會有太多的時(shí)間來進(jìn)行優(yōu)化(相對于其它語言的編譯器來說),因?yàn)榕c其它語言不同,javascript 的編譯過程不是發(fā)生在構(gòu)建之前的。
對于 javascript 來說,大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微秒(甚至更短)的時(shí)間內(nèi)。在我們將要討論的作用域背后,javascript 引擎用盡了各種辦法(比如 JIT,可以延遲編譯甚至重新編譯)來保證性能最佳。
總結(jié)來說,任何 javascript 代碼片段在執(zhí)行前都要進(jìn)行編譯(預(yù)編譯)。因此,javascript 編譯器首先會對 var a = 5; 這段程序進(jìn)行編譯,然后做好執(zhí)行它的準(zhǔn)備,并且通常馬上就會執(zhí)行它。
2. 三位好友要真正理解作用域,我們首先要知道 javascript 中有三位好朋友:
引擎
從頭到尾負(fù)責(zé)整個(gè) javascript 程序的編譯及執(zhí)行過程。
編譯器
負(fù)責(zé)語法分析及代碼生成。
作用域
負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識符(變量)組成的一系列查詢,并實(shí)施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對這些標(biāo)識符的訪問權(quán)限。
當(dāng)遇見 var a = 5; 這一段代碼時(shí),其實(shí)執(zhí)行了兩個(gè)步驟:
(1)var a; 編譯器會詢問作用域是否已經(jīng)有一個(gè)該名稱的變量存在于同一作用域的集合中。如果是,編譯器會忽略該聲明,繼續(xù)進(jìn)行編譯,否則它會要求在當(dāng)前作用域的集合中聲明一個(gè)新的變量,并命名為 a 。
(2)a = 5; 編譯器會為引擎生成運(yùn)行時(shí)所需的代碼,這些代碼用來處理 a = 5; 這個(gè)賦值操作。引擎運(yùn)行時(shí)會首先詢問作用域,在當(dāng)前作用域的集合中是否存在一個(gè)叫作 a 的變量,如果是,引擎就會使用這個(gè)變量。如果否,引擎會繼續(xù)向父級作用域中查找,直到找到全局作用域,如果在全局作用域中仍沒有找到 a ,那么在非嚴(yán)格模式下,引擎會為全局對象新建一個(gè)屬性 a ,并將其賦值為5,在嚴(yán)格模式下,引擎會報(bào)錯誤 ReferenceError: a is not defined。
總結(jié)來說,變量的賦值會執(zhí)行兩個(gè)操作,首先編譯器會在當(dāng)前作用域聲明一個(gè)變量(如果之前沒有聲明過),然后在運(yùn)行時(shí)引擎會在當(dāng)前作用域中查找該變量(找不到就向上一級作用域查找),如果能夠找到就會對它賦值。
3. LHS 和 RHS前面說到引擎在為變量賦值的時(shí)候會在作用域中查找變量,但是執(zhí)行怎樣的查找,用什么方式,會對最終的查找結(jié)果造成影響。
在 var a = 5; 這個(gè)例子中,引擎會對 a 進(jìn)行 LHS 查詢,當(dāng)然,另外一個(gè)查找類型叫作 RHS。
對變量進(jìn)行賦值所執(zhí)行的查詢叫 LHS。
找到并使用變量值所執(zhí)行的查詢叫 RHS。
舉個(gè)例子:
function foo(a) { // 這里隱式包含了 a = 2 這個(gè)賦值,所以對 a 進(jìn)行了 LHS 查詢 var b = a; // 這里對 a 進(jìn)行了 RHS 查詢,找到 a 的值,然后對 b 進(jìn)行 LHS 查詢,把 2 賦值給 b return a + b; // 這里包含了對 a 和 b 進(jìn)行的 RHS 查詢 } var c = foo(2); // 這里首先對 foo 進(jìn)行 RHS 查詢,找到它是一個(gè)函數(shù),然后對 c 進(jìn)行 LHS 查詢把 foo 賦值給 c
所以上面的例子共包含 3 個(gè) LHS 查詢和 4 個(gè) RHS 查詢,你們都找對了嗎?
4. 作用域嵌套當(dāng)一個(gè)塊或函數(shù)嵌套在另一個(gè)塊或函數(shù)中時(shí),就發(fā)生了作用域嵌套。因此,在當(dāng)前作用域中無法找到某個(gè)變量時(shí),引擎就會在外層嵌套的作用域中繼續(xù)查找,直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止。
舉個(gè)例子:
function foo(a) { console.log(a + b); } var b = 2; foo(2); // 4
這里對 b 進(jìn)行的 RHS 查詢在 foo 作用域中無法找到,但可以在上一級作用域(這個(gè)例子中就是全局作用域)中找到。
總結(jié)來說,遍歷嵌套作用域鏈的規(guī)則很簡單:引擎從當(dāng)前執(zhí)行的作用域中開始查找變量,如果都找不到,就向上一級繼續(xù)查找。當(dāng)?shù)诌_(dá)最外層的全局作用域時(shí),無論找到還是沒找到,查找過程都會停止。
5. 總結(jié)編譯器、引擎和作用域是 javascript 代碼執(zhí)行的基礎(chǔ),掌握好這些會對我們深入學(xué)習(xí) javascript 起到事半功倍的效果,我們的學(xué)習(xí)之路才剛剛開始,大家加油!
歡迎關(guān)注我的公眾號 參考文章《你不知道的JavaScript》
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86752.html
摘要:第二個(gè)賦值聲明會被留在原地等待執(zhí)行階段。這個(gè)過程就叫作提升。還有一點(diǎn),函數(shù)聲明會被提升,但是函數(shù)表達(dá)式不會被提升。 到目前為止,大家應(yīng)該很熟悉作用域的概念了,以及根據(jù)聲明的位置和方式將變量分配給作用域的相關(guān)原理了。函數(shù)作用域和塊作用域的行為是一樣的,可以總結(jié)為:任何聲明在某個(gè)作用域內(nèi)的變量,都將屬于這個(gè)作用域。 但是作用域同其中的變量聲明出現(xiàn)的位置有某種微妙的關(guān)系,而這個(gè)細(xì)節(jié)就是我們這...
摘要:但是它們其實(shí)并不是二選一的關(guān)系并不是只能用或者。正因?yàn)槿绱耍噶钣袝r(shí)也被稱為虛擬指令。這是因?yàn)槭遣捎没跅5奶摂M機(jī)的機(jī)制。聲明模塊的全局變量。。下文預(yù)告現(xiàn)在你已經(jīng)了解了模塊的工作原理,下面將會介紹為什么運(yùn)行的更快。 作者:Lin Clark 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58c77641a6d8...
摘要:的抽象語法樹中可能會有一個(gè)叫作的頂級節(jié)點(diǎn),接下來是一個(gè)叫作它的值是的子節(jié)點(diǎn),以及一個(gè)叫作的子節(jié)點(diǎn)。值得注意的是,是非常重要的異常類型。嚴(yán)格模式下,未聲明的和倆者行為相同,都會是。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡單易用的語言,又是一門具有許多復(fù)雜微妙技術(shù)的語言,即使是經(jīng)驗(yàn)豐富的 JavaScript 開發(fā)者,如果...
摘要:編譯原理在傳統(tǒng)編譯語言的流程中,程序中的一段源代碼在執(zhí)行之前會經(jīng)歷三個(gè)步驟,統(tǒng)稱為編譯。定義聲明是在編譯階段進(jìn)行的,而賦值是在執(zhí)行階段進(jìn)行的。 編譯原理 在傳統(tǒng)編譯語言的流程中,程序中的一段源代碼在執(zhí)行之前會經(jīng)歷三個(gè)步驟,統(tǒng)稱為編譯。 詞法分析將由字符組成的字符串分解成(對編程語言來說)有意義的代碼塊,這些代碼塊被稱為詞法單元(token)。 語法分析這個(gè)過程是將詞法單元流(數(shù)組)轉(zhuǎn)...
摘要:寫在前面深入系列共計(jì)篇已經(jīng)正式完結(jié),這是一個(gè)旨在幫助大家,其實(shí)也是幫助自己捋順底層知識的系列。深入系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵指正。 寫在前面 JavaScript 深入系列共計(jì) 15 篇已經(jīng)正式完結(jié),這是一個(gè)旨在幫助大家,其實(shí)也是幫助自己捋順 JavaScript 底層知識的系列。重點(diǎn)講解了如原型、作用域、執(zhí)行上下文、變量對象、this、...
閱讀 880·2021-11-15 11:38
閱讀 1599·2021-09-24 09:48
閱讀 838·2021-09-24 09:47
閱讀 2270·2021-08-26 14:15
閱讀 3497·2019-08-30 11:09
閱讀 2601·2019-08-29 16:55
閱讀 1582·2019-08-26 14:01
閱讀 3032·2019-08-23 16:47