一禁舷、編譯器、引擎、作用域
眾所周知罢吃,JavaScript是一門編譯語言矾柜,簡(jiǎn)單的說,任何JavaScript代碼片段在執(zhí)行前都要進(jìn)行編譯
以片段var a = 2
做一個(gè)示例泊业、JavaScript編譯器首先會(huì)對(duì)var a = 2
這段程序進(jìn)行編譯把沼,然后做好執(zhí)行它的準(zhǔn)備,并且通常馬上就會(huì)執(zhí)行它吁伺。首先需要先了解JavaScript編譯器中的幾個(gè)重要角色饮睬。
引擎:從頭到尾負(fù)責(zé)整個(gè)JavaScript程序的編譯及執(zhí)行過程。
編譯器:引擎的好朋友之一篮奄,負(fù)責(zé)語法分析及代碼生成等臟活累活
作用域:引擎的另一個(gè)好朋友捆愁,負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢,并實(shí)施一套非常嚴(yán)格的規(guī)則窟却,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符的訪問權(quán)限昼丑。
1、當(dāng)你看到 var = 2
這段程序時(shí)夸赫,編譯器會(huì)詢問作用域是否存在一個(gè)該名稱的變量存在于同一個(gè)作用域的集合中菩帝,如果是,編譯器會(huì)忽略該聲明,繼續(xù)進(jìn)行編譯呼奢;否則他會(huì)要求作用域在當(dāng)前作用域的集合中聲明一個(gè)新的變量宜雀,并命名為a
2、加下來編譯器會(huì)為引擎生成運(yùn)行時(shí)所需的代碼握础,這些代碼被用來處理a=2這個(gè)賦值的操作辐董,引擎運(yùn)行時(shí)會(huì)首先詢問作用域,在當(dāng)前的作用域集合中是否存在一個(gè)叫a的變量禀综,如果是简烘,引擎就會(huì)使用這個(gè)變量,如果否定枷,引擎會(huì)繼續(xù)查找該變量(查找規(guī)則后面文章會(huì)具體描述)
如果引擎最終找到了a變量孤澎,就會(huì)將2賦值給它。否則引擎就會(huì)拋出一個(gè)異常依鸥。
總結(jié):變量的賦值操作會(huì)執(zhí)行兩個(gè)動(dòng)作亥至,首先編譯器會(huì)在當(dāng)前作用域中聲明一個(gè)變量(如果之前沒聲明過)
,然后再運(yùn)行時(shí)引擎會(huì)在作用域中查找該變量贱迟,如果能找到就會(huì)對(duì)它賦值姐扮。
引擎查找變量的規(guī)則,分為兩種衣吠,LHS 查詢和RHS 查詢茶敏。查找規(guī)則不同,會(huì)影響最終的查找結(jié)果缚俏。
LHR:當(dāng)變量出現(xiàn)在賦值操作的左側(cè)時(shí)進(jìn)行LHS查詢惊搏,
// LHR
// 找到變量的容器,從而對(duì)其進(jìn)行賦值
a = 2
RHS: 非左側(cè)時(shí)忧换,即RHS恬惯,意為‘得到xx的值’, 進(jìn)行RHS查詢
// RHS
// 沒有對(duì)a進(jìn)行賦值,需要查找并取得a的值亚茬,這樣才能將值傳遞給console.log()
console.log(a)
eg:
function foo(a){
console.log(a) // 2
}
foo(2)
引擎:我說作用域酪耳,我需要為foo進(jìn)行RHS引用。你見過它嗎刹缝?
作用域:別說碗暗,我還真見過,編譯器那小子剛剛聲明了它梢夯。它是一個(gè)函數(shù)言疗,給你。
引擎:哥們太夠意思了颂砸!好吧噪奄,我來執(zhí)行一下foo死姚。
引擎:作用域,還有個(gè)事兒勤篮。我需要為a進(jìn)行LHS引用知允,這個(gè)你見過嗎?
作用域:這個(gè)也見過叙谨,編譯器最近把它聲名為foo的一個(gè)形式參數(shù)了,拿去吧保屯。
引擎:大恩不言謝手负,你總是這么棒。現(xiàn)在我要把2賦值給a姑尺。
引擎:哥們竟终,不好意思又來打擾你。我要為console進(jìn)行RHS引用切蟋,你見過它嗎统捶?
作用域:咱倆誰跟誰啊,再說我就是干這個(gè)柄粹。這個(gè)我也有喘鸟,console是個(gè)內(nèi)置對(duì)象。給你驻右。
引擎:么么噠什黑。我得看看這里面是不是有l(wèi)og(..)。太好了堪夭,找到了愕把,是一個(gè)函數(shù)。
引擎:哥們森爽,能幫我再找一下對(duì)a的RHS引用嗎恨豁?雖然我記得它,但想再確認(rèn)一次爬迟。
作用域:放心吧橘蜜,這個(gè)變量沒有變動(dòng)過,拿走雕旨,不謝扮匠。
引擎:真棒。我來把a(bǔ)的值凡涩,也就是2棒搜,傳遞進(jìn)log(..)。
二活箕、作用域嵌套
作用域是根據(jù)名稱查找變量的一套規(guī)則力麸。
當(dāng)一個(gè)塊或函數(shù)嵌套在另一個(gè)塊或函數(shù)中時(shí),就發(fā)生了作用域的嵌套。因此克蚂,在當(dāng)前作用域中無法找到某個(gè)變量時(shí)闺鲸,引擎就會(huì)在外層嵌套的作用域中繼續(xù)查找,直到找到該變量埃叭,或抵達(dá)最外層的作用域(也就是全局作用域)為止摸恍。
function foo(a){
console.log(a+b)
}
var b = 2
foo(2) // 4
回顧一下前面的作用域和引擎之間的對(duì)話,上面代碼的對(duì)話如下赤屋。
引擎:foo的作用域兄弟立镶,你見過b嗎?我需要對(duì)它進(jìn)行RHS引用类早。
作用域:聽都沒聽過媚媒,走開。
引擎:foo的上級(jí)作用域兄弟涩僻,咦缭召?有眼不識(shí)泰山,原來你是全局作用域大哥逆日,太好了嵌巷。你見過b嗎?我需要對(duì)它進(jìn)行RHS引用室抽。
作用域:當(dāng)然了晴竞,給你吧。
遍歷嵌套作用域鏈的規(guī)則很簡(jiǎn)單:引擎從當(dāng)前的執(zhí)行作用域開始查找變量狠半,如果找不到噩死,就向上一級(jí)繼續(xù)查找。當(dāng)?shù)诌_(dá)最外層的全局作用域時(shí)神年,無論找到還是沒找到已维,查找過程都會(huì)停止。
可以把作用域鏈比喻成一個(gè)建筑
這個(gè)建筑代表程序中的嵌套作用域鏈已日。第一層樓代表當(dāng)前的執(zhí)行作用域垛耳,也就是你所處的位置。建筑的頂層代表全局作用域飘千。LHS和RHS引用都會(huì)在當(dāng)前樓層進(jìn)行查找堂鲜,如果沒有找到,就會(huì)坐電梯前往上一層樓护奈,如果還是沒有找到就繼續(xù)向上缔莲,以此類推。一旦抵達(dá)頂層(全局作用域)霉旗,可能找到了你所需的變量痴奏,也可能沒找到蛀骇,但無論如何查找過程都將停止。
三读拆、兩種查詢的區(qū)別擅憔?
在變量還沒有聲明的時(shí)候,這兩種查詢的行為是不一致的檐晕。
function foo(){
a = 0
console.log(b)
}
foo()
在上述代碼中暑诸,對(duì)于a是進(jìn)行的LHS查詢,對(duì)于b是進(jìn)行的RHS查詢辟灰。
如果LHS查詢?cè)谌肿饔糜蛑幸矡o法找到目標(biāo)變量屠列,全局作用域中就會(huì)熱心的創(chuàng)建一個(gè)具有該名稱的變量(非嚴(yán)格模式下會(huì)熱心創(chuàng)建,嚴(yán)格模式下禁止自動(dòng)或隱式的創(chuàng)建全局變量)伞矩。
如果RHS查詢?cè)谌肿饔糜蛑兴褜げ坏剿璧淖兞浚婢蜁?huì)拋出一個(gè)ReferenceError異常夏志。
如果你對(duì)RHS查詢到的變量進(jìn)行了不合理的操作乃坤,比如對(duì)一個(gè)非函數(shù)類型的值進(jìn)行函數(shù)調(diào)用等,那么引擎會(huì)拋出TypeError的異常沟蔑,
ReferenceError同作用域判別失敗相關(guān)湿诊,而TypeError則代表作用域判別成功了,但是對(duì)結(jié)果的操作是非法或不合理的瘦材。
老鐵厅须,覺得有用點(diǎn)贊。
參考資料:
《你不知道的JavaScript》