原文地址 https://medium.com/jspoint/how-javascript-works-in-browser-and-node-ab7d0d09ac2f
JavaScript概要
JavaScript是一種解釋性的語言,瀏覽器讀取源代碼,然后執(zhí)行。
JavaScript是動(dòng)態(tài)類型語言。靜態(tài)類型語言例如C或者Java,必須顯示聲明int,或者string。而在動(dòng)態(tài)類型語言JavaScript中,所有類型數(shù)據(jù)int,string以及復(fù)雜數(shù)據(jù)都都可以用var 來申明
JavaScript的歷史
在Web剛剛興起時(shí),web頁面是靜態(tài)的,只展示,沒有和用戶的交互。為了滿足和用戶交互的需求,當(dāng)時(shí)大名鼎鼎的網(wǎng)景瀏覽器(Netscape)在1995年引入了一個(gè)新的語言(當(dāng)時(shí)命名LiveScript),這個(gè)就是后來的JavaScript。這個(gè)語言的第一個(gè)版本只花了網(wǎng)景工程師Brendan Eich十天的時(shí)間。
同時(shí)期出現(xiàn)的還有其他語言,ActionScript,Silverlight,Flash等等,但最終JavaScript贏得了比賽。
JavaScript引擎的解刨
EcmaScript標(biāo)準(zhǔn)規(guī)定了瀏覽器應(yīng)該怎么實(shí)現(xiàn)JavaScript.但是并沒有規(guī)定JavaScript應(yīng)該在瀏覽器里面怎么運(yùn)行,所以不同的瀏覽器廠商自己決定怎么實(shí)現(xiàn)JavaScript。
每個(gè)瀏覽器實(shí)現(xiàn)一個(gè)JavaScript引擎。Netscape公司使用SpiderMonkey。這個(gè)引擎使用最原始的解釋器并且沒有任何優(yōu)化。JavaScript運(yùn)行起來非常慢

從上圖可以看到原始的JavaScript引擎包含一個(gè)基線編譯器,把JavaScript源代碼編譯成中間代碼(Intermediate representation),也就是字節(jié)碼,喂給解釋器。
解釋器把字節(jié)碼轉(zhuǎn)換成機(jī)器碼,最終在在CPU上執(zhí)行。
基線編譯器的工作是盡可能快的產(chǎn)生字節(jié)碼,由于喂給解釋器的字節(jié)碼沒有經(jīng)過優(yōu)化,程序運(yùn)行起來很慢,但是啟動(dòng)快。
當(dāng)動(dòng)態(tài)交互越來越多時(shí),上面的技術(shù)產(chǎn)生的用戶體驗(yàn)就會(huì)非常差。Google的Chrome瀏覽器在展示GoogleMap時(shí),就遇到這個(gè)問題,然后他們提出了V8 JavaScript引擎來解決這個(gè)問題。

在2010版本的V8 JavaScript引擎中,主要有兩個(gè)模塊,如上圖所示full-codegen是基線編譯器,為了提高程序的啟動(dòng)速度,盡可能快的產(chǎn)生機(jī)器碼。當(dāng)程序運(yùn)行過車中,crankshaft編譯器插入進(jìn)來,優(yōu)化源代碼并且把源代碼中可以優(yōu)化的部分產(chǎn)生的機(jī)器碼替換掉full-codegen產(chǎn)生的部分。
JavaScript怎么被優(yōu)化的
JavaScript有很多種被優(yōu)化的標(biāo)準(zhǔn),當(dāng)JavaScript被傳入基線編譯器或者解釋器的時(shí)候,必須先被轉(zhuǎn)換成抽象法樹(Abstract Syntax Tree,簡稱AST)
當(dāng)我們運(yùn)行JavaScript程序時(shí),啟動(dòng)時(shí)并不需要所有的代碼,例如用戶點(diǎn)擊才會(huì)觸發(fā)的函數(shù),那個(gè)函數(shù)可以在被點(diǎn)擊時(shí)才被解析。
識(shí)別需要立即解析并生成機(jī)器代碼的內(nèi)容是加快應(yīng)用程序引導(dǎo)速度的最佳策略。
JavaScript沒有類型系統(tǒng)的特征使JavaScript引擎產(chǎn)生優(yōu)化程度較低的代碼。所以,基于已經(jīng)賦值的值,JavaScript Engine可以猜測它的類型產(chǎn)生優(yōu)化程度較高的代碼。
Paul Ryan在他關(guān)于V8引擎的博文對整個(gè)過程做了很好的說明。有興趣深入研究的同學(xué)可以了解下。
與此同時(shí),JavaScript引擎還可以收集代碼執(zhí)行的分析數(shù)據(jù),并尋找運(yùn)行較慢的代碼。這中代碼被稱為"熱"代碼,可能是因?yàn)樗容^費(fèi)CPU。這種代碼可以進(jìn)一步優(yōu)化,并用優(yōu)化后的機(jī)器碼替換。
考慮大上面這些問題已經(jīng)full-codegen和crankshaft產(chǎn)生的其他問題,V8團(tuán)隊(duì)從頭開始做了一款新的V8引擎。2017年發(fā)布。

)
從上圖可以看出,V8團(tuán)隊(duì)引入新的解釋器管道Ignition來執(zhí)行編譯的過程。首先才能夠用基線編譯器從JavaScript源代碼產(chǎn)生字節(jié)碼,然后用解釋器去解釋這個(gè)字節(jié)碼,最終產(chǎn)生機(jī)器碼。
當(dāng)程序在運(yùn)行的時(shí)候, TurboFan優(yōu)化編譯器在后臺(tái)優(yōu)化基線編譯器產(chǎn)生的字節(jié)碼,產(chǎn)生一個(gè)優(yōu)化后的機(jī)器碼,最終替換原來的版本的機(jī)器碼。
Turbofan接受Ignition解釋器代碼執(zhí)行的分析數(shù)據(jù),并且查看對于的代碼是否是“熱”代碼(是否可以優(yōu)化)。然后優(yōu)化對應(yīng)的代碼
其他的JavaScript引擎
現(xiàn)在我們大概知道了V8 JavaScript引擎是怎么工作的。其他瀏覽器制造商的其他引擎是采用類似的模型。例如網(wǎng)景和火狐采用的的SpiderMonkey,F(xiàn)irefox的Chakra等。
除了Google Chrome瀏覽器,Chromium項(xiàng)目,Electron.js 以及Node.js都采用V8引擎。
JavaScript運(yùn)行時(shí)
JavaScript是一個(gè)運(yùn)行時(shí)多線程語言。就是同一時(shí)間只能有一條指令執(zhí)行。
當(dāng)你打開一個(gè)網(wǎng)站的時(shí)候,他用一個(gè)線程執(zhí)行JavaScript,這個(gè)線程負(fù)責(zé)處理所有的事情,例如抓取web頁面,在web頁面上輸出,監(jiān)聽Dom事件等等。
當(dāng)JavaScript執(zhí)行被阻塞時(shí),瀏覽器就會(huì)卡在這個(gè)點(diǎn)。例如死循環(huán)等。
有一些現(xiàn)代瀏覽器每個(gè)tab或者每個(gè)domain使用一個(gè)JavaScript線程。這樣在一個(gè)頁面上的阻塞,只會(huì)阻礙當(dāng)前頁面。
我們用一小段代碼來說明JavaScript是怎么執(zhí)行程序的,理解JavaScript的運(yùn)行時(shí),以及不同的模塊怎么參與的
function baz() {
console.log( 'Hello from baz' );
}
function bar() {
baz();
}
function foo() {
bar();
}
foo();
它的調(diào)用棧是這樣的:

同時(shí),把這段代碼放到瀏覽器中執(zhí)行,得到的調(diào)用棧如下

后續(xù)
后續(xù)會(huì)補(bǔ)充瀏覽器中的線程模型