JS學(xué)習(xí)系列 01 - 編譯原理和作用域

在學(xué)習(xí) javascript 的過程中,我們第一步最應(yīng)該了解和掌握的就是作用域亭螟,與之相關(guān)還有程序是怎么編譯的残家,變量是怎么查找的榆俺,js 引擎是什么,引擎和作用域的關(guān)系又是什么,這些是 javascript 這門語言最基礎(chǔ)的地基茴晋,至于對象迂求、函數(shù)、閉包晃跺、原型鏈揩局、作用域鏈以及設(shè)計模式等等都是地基以上的建筑,只有地基打牢了掀虎,建筑才會穩(wěn)凌盯。同樣只有先把最基礎(chǔ)的部分掌握了,之后的擴展學(xué)習(xí)才會更容易烹玉。

這一節(jié)我要說的驰怎,就是作用域和編譯原理,從這里開始二打,我會一點點的把深入學(xué)習(xí) javascript 的過程中總結(jié)的知識點以及遇到的問題县忌,一篇一篇的梳理出來,如果有志同道合的朋友继效,可以關(guān)注我這個系列症杏,我們一起玩轉(zhuǎn) javascript。

1. 編譯原理

大家通常把 javascript 歸類為一種“動態(tài)”或“解釋執(zhí)行”的語言瑞信,但事實上厉颤,它是一門編譯語言,但和傳統(tǒng)的編譯語言不同凡简,它不是提前編譯的逼友,編譯結(jié)果也不能進行移植。

在傳統(tǒng)編譯語言中秤涩,程序在執(zhí)行之前會經(jīng)歷三個步驟帜乞,統(tǒng)稱為“編譯”:

  • 分詞/詞法分析
    這個過程會把字符串分解成有意義的代碼塊,這些代碼塊被稱為詞法單元筐眷。
    例如 var a = 5; 這段程序通常會被分解成下面這些詞法單元: var黎烈、a、=浊竟、5怨喘、; 津畸≌穸ǎ空格是否會被當(dāng)成詞法單元取決于空格在這門語言中是否有意義。
  • 解析/語法分析
    這個過程是將詞法單元流(數(shù)組)轉(zhuǎn)換成一個由元素逐級嵌套所組成的代表了程序語法結(jié)構(gòu)的樹肉拓。這個樹被稱為“抽象語法樹”(Abstract Syntax Tree后频,AST)。
    var a = 5; 的抽象語法樹中可能如下圖所示:
    抽象語法樹
  • 代碼生成
    將 AST 轉(zhuǎn)換為可執(zhí)行代碼的過程被稱為代碼生成。這個過程與語言卑惜、目標(biāo)平臺等息息相關(guān)膏执。簡單來說,就是通過某種方法可以將 var a = 5; 的 AST 轉(zhuǎn)化為一組機器指令露久,用來創(chuàng)建一個叫做 a 的變量(包括分配內(nèi)存等)更米,并將一個值 5 存儲在 a 中。

比起那些編譯過程只有三個步驟的語言的編譯器來說毫痕,javascript 引擎要復(fù)雜的多征峦。
例如,在詞法分析和代碼生成階段有特定的步驟來對運行性能進行優(yōu)化消请,包括對冗余元素進行優(yōu)化等栏笆。

首先我們要清楚,javaScript 引擎不會有太多的時間來進行優(yōu)化(相對于其它語言的編譯器來說)臊泰,因為與其它語言不同蛉加,javascript 的編譯過程不是發(fā)生在構(gòu)建之前的

對于 javascript 來說缸逃,大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微秒(甚至更短)的時間內(nèi)针饥。在我們將要討論的作用域背后,javascript 引擎用盡了各種辦法(比如 JIT需频,可以延遲編譯甚至重新編譯)來保證性能最佳打厘。

總結(jié)來說,任何 javascript 代碼片段在執(zhí)行前都要進行編譯(預(yù)編譯)贺辰。因此户盯,javascript 編譯器首先會對 var a = 5; 這段程序進行編譯,然后做好執(zhí)行它的準(zhǔn)備饲化,并且通常馬上就會執(zhí)行它莽鸭。

2. 三位好友

要真正理解作用域,我們首先要知道 javascript 中有三位好朋友:

  • 引擎
    從頭到尾負(fù)責(zé)整個 javascript 程序的編譯及執(zhí)行過程吃靠。
  • 編譯器
    負(fù)責(zé)語法分析及代碼生成硫眨。
  • 作用域
    負(fù)責(zé)收集并維護由所有聲明的標(biāo)識符(變量)組成的一系列查詢,并實施一套非常嚴(yán)格的規(guī)則巢块,確定當(dāng)前執(zhí)行的代碼對這些標(biāo)識符的訪問權(quán)限礁阁。

當(dāng)遇見 var a = 5; 這一段代碼時,其實執(zhí)行了兩個步驟:

(1)var a; 編譯器會詢問作用域是否已經(jīng)有一個該名稱的變量存在于同一作用域的集合中族奢。如果是姥闭,編譯器會忽略該聲明,繼續(xù)進行編譯越走,否則它會要求在當(dāng)前作用域的集合中聲明一個新的變量棚品,并命名為 a 靠欢。
(2)a = 5; 編譯器會為引擎生成運行時所需的代碼,這些代碼用來處理 a = 5; 這個賦值操作铜跑。引擎運行時會首先詢問作用域门怪,在當(dāng)前作用域的集合中是否存在一個叫作 a 的變量,如果是锅纺,引擎就會使用這個變量掷空。如果否,引擎會繼續(xù)向父級作用域中查找囤锉,直到找到全局作用域拣帽,如果在全局作用域中仍沒有找到 a ,那么在非嚴(yán)格模式下嚼锄,引擎會為全局對象新建一個屬性 a 减拭,并將其賦值為5,在嚴(yán)格模式下区丑,引擎會報錯誤 ReferenceError: a is not defined拧粪。

總結(jié)來說,變量的賦值會執(zhí)行兩個操作沧侥,首先編譯器會在當(dāng)前作用域聲明一個變量(如果之前沒有聲明過)可霎,然后在運行時引擎會在當(dāng)前作用域中查找該變量(找不到就向上一級作用域查找),如果能夠找到就會對它賦值宴杀。

3. LHS 和 RHS

前面說到引擎在為變量賦值的時候會在作用域中查找變量癣朗,但是執(zhí)行怎樣的查找,用什么方式旺罢,會對最終的查找結(jié)果造成影響旷余。

var a = 5; 這個例子中,引擎會對 a 進行 LHS 查詢扁达,當(dāng)然正卧,另外一個查找類型叫作 RHS。

對變量進行賦值所執(zhí)行的查詢叫 LHS跪解。
找到并使用變量值所執(zhí)行的查詢叫 RHS炉旷。

舉個例子:

function foo(a) {
   // 這里隱式包含了 a = 2 這個賦值,所以對 a 進行了 LHS 查詢
   var b = a;
   // 這里對 a 進行了 RHS 查詢叉讥,找到 a 的值窘行,然后對 b 進行 LHS 查詢,把 2 賦值給 b
   return a + b; 
   // 這里包含了對 a 和 b 進行的 RHS 查詢
}

var c = foo(2);
// 這里首先對 foo 進行 RHS 查詢图仓,找到它是一個函數(shù)罐盔,然后對 c 進行 LHS 查詢把 foo 賦值給 c 

所以上面的例子共包含 3 個 LHS 查詢和 4 個 RHS 查詢,你們都找對了嗎透绩?

4. 作用域嵌套

當(dāng)一個塊或函數(shù)嵌套在另一個塊或函數(shù)中時翘骂,就發(fā)生了作用域嵌套。因此帚豪,在當(dāng)前作用域中無法找到某個變量時碳竟,引擎就會在外層嵌套的作用域中繼續(xù)查找,直到找到該變量狸臣,或抵達最外層的作用域(也就是全局作用域)為止莹桅。

舉個例子:

function foo(a) {
   console.log(a + b);
}

var b = 2;

foo(2);    // 4

這里對 b 進行的 RHS 查詢在 foo 作用域中無法找到,但可以在上一級作用域(這個例子中就是全局作用域)中找到烛亦。

總結(jié)來說诈泼,遍歷嵌套作用域鏈的規(guī)則很簡單:引擎從當(dāng)前執(zhí)行的作用域中開始查找變量,如果都找不到煤禽,就向上一級繼續(xù)查找铐达。當(dāng)?shù)诌_最外層的全局作用域時,無論找到還是沒找到檬果,查找過程都會停止瓮孙。

5. 總結(jié)

編譯器、引擎和作用域是 javascript 代碼執(zhí)行的基礎(chǔ)选脊,掌握好這些會對我們深入學(xué)習(xí) javascript 起到事半功倍的效果杭抠,我們的學(xué)習(xí)之路才剛剛開始,大家加油恳啥!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末偏灿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钝的,更是在濱河造成了極大的恐慌翁垂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硝桩,死亡現(xiàn)場離奇詭異沮峡,居然都是意外死亡,警方通過查閱死者的電腦和手機亿柑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門邢疙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人望薄,你說我怎么就攤上這事疟游。” “怎么了痕支?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵颁虐,是天一觀的道長。 經(jīng)常有香客問我卧须,道長另绩,這世上最難降的妖魔是什么儒陨? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮笋籽,結(jié)果婚禮上蹦漠,老公的妹妹穿的比我還像新娘。我一直安慰自己车海,他們只是感情好笛园,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著侍芝,像睡著了一般研铆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上州叠,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天棵红,我揣著相機與錄音,去河邊找鬼咧栗。 笑死窄赋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楼熄。 我是一名探鬼主播忆绰,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼可岂!你這毒婦竟也來了错敢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤缕粹,失蹤者是張志新(化名)和其女友劉穎稚茅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體平斩,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡亚享,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绘面。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欺税。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖揭璃,靈堂內(nèi)的尸體忽然破棺而出晚凿,到底是詐尸還是另有隱情,我是刑警寧澤瘦馍,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布歼秽,位于F島的核電站,受9級特大地震影響情组,放射性物質(zhì)發(fā)生泄漏燥筷。R本人自食惡果不足惜箩祥,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肆氓。 院中可真熱鬧袍祖,春花似錦、人聲如沸做院。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽键耕。三九已至,卻和暖如春柑营,著一層夾襖步出監(jiān)牢的瞬間屈雄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工官套, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酒奶,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓奶赔,卻偏偏與公主長得像惋嚎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子站刑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容