作用域
一套設(shè)計(jì)良好的規(guī)則來(lái)存儲(chǔ)變量,并且方便之后再找到變量。這套規(guī)則叫做作用域。
1. 編譯原理
通常認(rèn)為JavaScript是“動(dòng)態(tài)”||“解釋執(zhí)行”的語(yǔ)言,事實(shí)上是一門編譯語(yǔ)言。不過(guò)其并非提前編譯的,編譯的結(jié)果也不能再分布式系統(tǒng)中移植運(yùn)行。
JavaScript引擎編譯步驟
- 分詞/詞法分析
將字符串分解為(對(duì)編程語(yǔ)言來(lái)說(shuō))有意義的代碼塊(即詞法單元) - 解析/語(yǔ)法分析
將詞法單元流(數(shù)組)轉(zhuǎn)換成“抽象語(yǔ)法樹(shù)”(AST) - 代碼生成
將AST轉(zhuǎn)換為可執(zhí)行代碼的過(guò)程為代碼生成
簡(jiǎn)單來(lái)說(shuō)就是有某種方法可以將
var a = 2;的AST 轉(zhuǎn)化為一組機(jī)器指令,用來(lái)創(chuàng)建一個(gè)叫作a 的變量(包括分配內(nèi)存等),并將一個(gè)值儲(chǔ)存在a 中。
任何JavaScript代碼片段在執(zhí)行前都要編譯,大多是發(fā)生在執(zhí)行前幾微秒。
2. 理解作用域
- 引擎
從頭到尾負(fù)責(zé)整個(gè)JavaScript程序的編譯及執(zhí)行過(guò)程 - 編譯器
輔助詞法分析&代碼生成 - 作用域
負(fù)責(zé)收集&維護(hù)所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢,并實(shí)施非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行代碼對(duì)這些變量的訪問(wèn)權(quán)限。
對(duì)var a = 2;的分解
- 編譯器先將這段代碼分解為詞法單元,再將詞法單元解析成一個(gè)樹(shù)結(jié)構(gòu)
- 遇到
var a,編譯器會(huì)詢問(wèn)作用域是否存在同名變量。是->忽略該聲明,繼續(xù)編譯;否->在當(dāng)前作用域的集合中聲明一個(gè)新的變量,并命名為a - 接下來(lái),編譯器會(huì)為引擎生成運(yùn)行時(shí)所需代碼,用來(lái)處理
a = 2的操作。引擎運(yùn)行時(shí)會(huì)先詢問(wèn)作用域,在當(dāng)前作用域集合中是否存在叫a的變量,是->引擎使用此變量;否->引擎繼續(xù)查找;如未找到,會(huì)拋出異常
總結(jié):變量的賦值會(huì)執(zhí)行兩個(gè)動(dòng)作,首先在當(dāng)前作用域中聲明變量(如之前未聲明過(guò)),然后運(yùn)行時(shí),引擎在作用域中查找該變量,如果找到則進(jìn)行賦值。
LHS查詢&RHS查詢
根據(jù)變量在賦值操作(=)的左右側(cè)分為,LHS和RHS
- RHS是取值,查找源值
- LHS是取容器,找到目標(biāo)
函數(shù)聲明function foo(a) {...不是LHS查詢
而var foo、 foo = function(a) {...是LHS查詢
function foo(a) {
var b = a;
return a + b;
}
var c = foo( 2 );
這其中有3處LHS和4出RHS
LHS: c=... | a=... | b=...
RHS: foo(2) | =a | a+ | +b
3. 作用域嵌套
在當(dāng)前作用域中無(wú)法找到某個(gè)變量,就會(huì)向外層作用域查找,直到找到該變量或抵達(dá)最外層的作用域(全局作用域)
4. 異常
ES5中引入了“嚴(yán)格模式”,在正常模式|“寬松模式“|”懶惰模式”下,如果找到全局作用域還是找不到變量,會(huì)自動(dòng)或隱式地創(chuàng)建全局變量。
而在“嚴(yán)格模式”下,不會(huì)創(chuàng)建并返回一個(gè)全局變量,引擎會(huì)拋出ReferenceError異常。
嚴(yán)格模式下,禁止自動(dòng)或隱式地創(chuàng)建全局變量
小結(jié)
- 作用域是一套規(guī)則,用于確定在何處以及如何查找變量(標(biāo)識(shí)符)
- 查找的目的是對(duì)變量賦值,則是LHS查詢;目的是獲取變量的值則是RHS查詢