JS編譯原理
JavaScript是一門可歸類于"動(dòng)態(tài)"或"解釋執(zhí)行"的編程語(yǔ)言掖举,與傳統(tǒng)的編程語(yǔ)言不同,它不是提前編譯的,編譯結(jié)果也不能在分布式系統(tǒng)中移植仪芒。通常一段源代碼在執(zhí)行之前會(huì)經(jīng)歷三個(gè)過(guò)程:
分詞/詞法分析
這個(gè)過(guò)程會(huì)將字符串分割為有意義的代碼塊,這些代碼塊稱之為詞法單元耕陷。例如變量的聲明:
var a = 2;
這行代碼會(huì)被分為以下詞法單元:var掂名、a、=哟沫、2(空格算不算詞法單元取決于空格對(duì)于該編程語(yǔ)言是否具有意義)饺蔑;這些零散的詞法單元會(huì)組成一個(gè)詞法單元流(數(shù)組)進(jìn)行解析。
解析/與法分析
這個(gè)過(guò)程會(huì)將詞法單元流轉(zhuǎn)換成一棵抽象語(yǔ)法樹(Abstract Syntax Tree嗜诀,AST)在線解析工具猾警。
var a = 2;
的詞法單元流就會(huì)被解析為下面的AST:
代碼生成
將AST轉(zhuǎn)化為可執(zhí)行的代碼孔祸。
整個(gè)過(guò)程看似很簡(jiǎn)單,但是在語(yǔ)法分析和代碼生成階段有很多坑等著踩肿嘲。
這里又要引入幾個(gè)概念了
成員:
1.引擎:負(fù)責(zé)整個(gè)過(guò)程中javascript的編譯及執(zhí)行過(guò)程融击。瀏覽器不同,其引擎也不同雳窟,比如Chrome采用的是v8尊浪,Safari采用的是SquirrelFish Extreme。
2.編譯器:負(fù)責(zé)語(yǔ)法分析和代碼生成封救。
3.作用域:負(fù)責(zé)收集并維護(hù)所有的標(biāo)識(shí)符(變量)簡(jiǎn)析JavaScript中的作用域與作用域鏈
例子分析:
還是對(duì)最簡(jiǎn)單的例子進(jìn)行分析拇涤,var a = 2;
,首先進(jìn)行詞法分析誉结,然后將詞法單元流交給編譯器生成AST鹅士,再有編譯器生成可執(zhí)行的代碼。
???A.編譯器遇到var a;
會(huì)詢問(wèn)同一作用域集是否有存在同名的變量惩坑,如果有掉盅,就忽略該聲明,繼續(xù)編譯以舒;如果沒(méi)有編譯器就會(huì)要求作用域在當(dāng)前作用域的集合生命一個(gè)新的變量趾痘,并命名為a。
???B.編譯器會(huì)為引擎的運(yùn)行生成一些列代碼蔓钟,這些代碼用于為變量a進(jìn)行賦值操作永票。引擎會(huì)詢問(wèn)當(dāng)前作用域是否有這個(gè)變量的存在,如果有則進(jìn)行賦值操作滥沫,如果沒(méi)有就開始查找這個(gè)變量(從當(dāng)前作用域向上查找侣集,直到全局作用域,如果還是沒(méi)有兰绣,就會(huì)拋出一個(gè)異常)世分。
???C.LHS和RHS,當(dāng)引擎執(zhí)行編譯器給的代碼(賦值操作)時(shí),會(huì)通過(guò)查找這個(gè)變量來(lái)判斷這個(gè)變量是否已經(jīng)聲明,這個(gè)過(guò)程需要作用域的協(xié)助缀辩,而查找的方式分為兩種:LHS(“賦值操作的目標(biāo)是誰(shuí)”)臭埋、RHS(”誰(shuí)是賦值操作的源頭“)。例如下面這個(gè)例子:
var a; //RHS引用
a = 2; //LHS引用
alert(a); //RHS引用
/*這段代碼塊既有RHS引用也有LHS引用雌澄,
foo(2),2被當(dāng)作函數(shù)參數(shù)傳遞給foo()時(shí)杯瞻,2會(huì)被分配給變量a(a = 2);
*/
function foo(a){
alert(a);
}
foo(2);
區(qū)分RHS和LHS也很重要镐牺,尤其分析異常時(shí)。例如下面這個(gè)例子:
function foo(a){
alert(a + b);
b = a;
}
foo(2);
第一次對(duì)b進(jìn)行RHS查詢會(huì)查詢不到這個(gè)變量魁莉,因?yàn)樗且粋€(gè)未聲明的變量睬涧,在所有作用域都無(wú)法找到(var b;);此時(shí)引擎會(huì)拋出一個(gè)異常(ReferenceError)募胃。在非嚴(yán)格模式下,當(dāng)引擎進(jìn)行LHS查詢查詢不到某個(gè)變量時(shí)畦浓,全局作用域會(huì)創(chuàng)建一個(gè)同名的變量交給引擎痹束,當(dāng)然這個(gè)變量具有全局作用域;而在嚴(yán)格模式下,引擎會(huì)拋出ReferenceError的異常讶请。
提升
變量和函數(shù)在內(nèi)的聲明都在任何代碼執(zhí)行前被處理祷嘶。聲明操作在編譯階段時(shí)進(jìn)行的,而賦值操作是在等到執(zhí)行階段才執(zhí)行夺溢。
//代碼塊1
var a = 2;
alert(a); // 輸出2
//代碼塊2
b = 2;
var b;
alert(b); //輸出2
//代碼塊3
alert(c); //輸出undefined
var c = 2;
//代碼塊4
var d;
alert(d); //輸出undefined
d = 2;
代碼塊2论巍,4等價(jià)于代碼塊1,3(除了變量名不同风响,內(nèi)存地址不同)嘉汰;這個(gè)過(guò)程就好像變量和函數(shù)聲明的代碼被移動(dòng)到了最上面,這個(gè)過(guò)程就叫提升状勤。
函數(shù)聲明可以提升鞋怀,函數(shù)表達(dá)式不能提升。
//函數(shù)聲明可以提升
foo(); //輸出2持搜;
function foo(){
alert(2);
//函數(shù)表達(dá)式不可提升
bar(); //TypeError
var bar = function f1(){
alert(2);
}
函數(shù)聲明優(yōu)先于變量聲明提升,出現(xiàn)在后面的函數(shù)聲明可以覆蓋之前的聲明密似。
foo(); //輸出3
function foo(){
alert(1);
}
var foo = function bar(){
alert(2);
}
function foo(){
alert(3);
}