特別說(shuō)明,為便于查閱箩兽,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS
幾乎所有語(yǔ)言的最基礎(chǔ)模型之一就是在變量中存儲(chǔ)值,并且在稍后取出或修改這些值的能力章喉。事實(shí)上汗贫,在變量中存儲(chǔ)值和取出值的能力,給程序賦予了 狀態(tài)秸脱。
如果沒(méi)有這樣的概念落包,一個(gè)程序雖然可以執(zhí)行一些任務(wù),但是它們將會(huì)受到極大的限制而且不會(huì)非常有趣摊唇。
但是在我們的程序中納入變量咐蝇,引出了我們現(xiàn)在將要解決的最有趣的問(wèn)題:這些變量 存活 在哪里?換句話說(shuō)巷查,它們被存儲(chǔ)在哪兒有序?而且,最重要的是岛请,我們的程序如何在需要它們的時(shí)候找到它們旭寿?
回答這些問(wèn)題需要一組明確定義的規(guī)則,它定義如何在某些位置存儲(chǔ)變量崇败,以及如何在稍后找到這些變量盅称。我們稱這組規(guī)則為:作用域。
但是后室,這些 作用域 規(guī)則是在哪里微渠、如何被設(shè)置的?
編譯器理論
根據(jù)你與各種編程語(yǔ)言打交道的水平不同咧擂,這也許是不證自明的逞盆,或者這也許令人吃驚,盡管 JavaScript 一般被劃分到“動(dòng)態(tài)”或者“解釋型”語(yǔ)言的范疇松申,但是其實(shí)它是一個(gè)編譯型語(yǔ)言云芦。它 不是 像許多傳統(tǒng)意義上的編譯型語(yǔ)言那樣預(yù)先被編譯好俯逾,編譯的結(jié)果也不能在各種不同的分布式系統(tǒng)間移植。
但是無(wú)論如何舅逸,JavaScript 引擎在實(shí)施許多與傳統(tǒng)的語(yǔ)言編譯器相同的步驟桌肴,雖然是以一種我們不易察覺(jué)的更精巧的方式。
在傳統(tǒng)的編譯型語(yǔ)言處理中琉历,一塊兒源代碼坠七,你的程序,在它被執(zhí)行 之前 通常將會(huì)經(jīng)歷三個(gè)步驟旗笔,大致被稱為“編譯”:
-
分詞/詞法分析: 將一連串字符打斷成(對(duì)于語(yǔ)言來(lái)說(shuō))有意義的片段彪置,稱為 token(記號(hào))。舉例來(lái)說(shuō)蝇恶,考慮這段程序:
var a = 2;
拳魁。這段程序很可能會(huì)被打斷成如下 token:var
,a
撮弧,=
潘懊,2
,和;
贿衍∈谥郏空格也許會(huì)被保留為一個(gè) token,這要看它是否是有意義的贸辈。注意: 分詞和詞法分析之間的區(qū)別是微妙和學(xué)術(shù)上的释树,其中心在于這些 token 是否以 無(wú)狀態(tài) 或 有狀態(tài) 的方式被識(shí)別。簡(jiǎn)而言之裙椭,如果分詞器去調(diào)用有狀態(tài)的解析規(guī)則來(lái)弄清
a
是否應(yīng)當(dāng)被考慮為一個(gè)不同的 token,還是只是其他 token 的一部分署浩,那么這就是 詞法分析揉燃。 -
解析: 將一個(gè) token 的流(數(shù)組)轉(zhuǎn)換為一個(gè)嵌套元素的樹(shù),它綜合地表示了程序的語(yǔ)法結(jié)構(gòu)筋栋。這棵樹(shù)稱為“抽象語(yǔ)法樹(shù)”(AST —— <b>A</b>bstract <b>S</b>yntax <b>T</b>ree)炊汤。
var a = 2;
的樹(shù)也許開(kāi)始于稱為VariableDeclaration
(變量聲明)頂層節(jié)點(diǎn),帶有一個(gè)稱為Identifier
(標(biāo)識(shí)符)的子節(jié)點(diǎn)(它的值為a
)弊攘,和另一個(gè)稱為AssignmentExpression
(賦值表達(dá)式)的子節(jié)點(diǎn)抢腐,而這個(gè)子節(jié)點(diǎn)本身帶有一個(gè)稱為NumericLiteral
(數(shù)字字面量)的子節(jié)點(diǎn)(它的值為2
)。 -
代碼生成: 這個(gè)處理將抽象語(yǔ)法樹(shù)轉(zhuǎn)換為可執(zhí)行的代碼襟交。這一部分將根據(jù)語(yǔ)言迈倍,它的目標(biāo)平臺(tái)等因素有很大的不同。
所以捣域,與其深陷細(xì)節(jié)啼染,我們不如籠統(tǒng)地說(shuō)宴合,有一種方法將我們上面描述的
var a = 2;
的抽象語(yǔ)法樹(shù)轉(zhuǎn)換為機(jī)器指令,來(lái)實(shí)際上 創(chuàng)建 一個(gè)稱為a
的變量(包括分配內(nèi)存等等)迹鹅,然后在a
中存入一個(gè)值卦洽。注意: 引擎如何管理系統(tǒng)資源的細(xì)節(jié)遠(yuǎn)比我們要挖掘的東西深刻,所以我們將理所當(dāng)然地認(rèn)為引擎有能力按其需要?jiǎng)?chuàng)建和存儲(chǔ)變量斜棚。
和大多數(shù)其他語(yǔ)言的編譯器一樣阀蒂,JavaScript 引擎要比這區(qū)區(qū)三步復(fù)雜太多了。例如弟蚀,在解析和代碼生成的處理中蚤霞,一定會(huì)存在優(yōu)化執(zhí)行效率的步驟,包括壓縮冗余元素粗梭,等等争便。
所以,我在此描繪的只是大框架断医。但是我想你很快就會(huì)明白為什么我們涵蓋的這些細(xì)節(jié)是重要的滞乙,雖然是在很高的層次上。
其一鉴嗤,JavaScript 引擎沒(méi)有(像其他語(yǔ)言的編譯器那樣)大把的時(shí)間去優(yōu)化斩启,因?yàn)?JavaScript 的編譯和其他語(yǔ)言不同,不是提前發(fā)生在一個(gè)構(gòu)建的步驟中醉锅。
對(duì) JavaScript 來(lái)說(shuō)兔簇,在許多情況下,編譯發(fā)生在代碼被執(zhí)行前的僅僅幾微秒之內(nèi)(或更少S菜!)垄琐。為了確保最快的性能,JS 引擎將使用所有的招數(shù)(比如 JIT经柴,它可以懶編譯甚至是熱編譯狸窘,等等),而這遠(yuǎn)超出了我們關(guān)于“作用域”的討論坯认。
為了簡(jiǎn)單起見(jiàn)翻擒,我們可以說(shuō),任何 JavaScript 代碼段在它執(zhí)行之前(通常是 剛好 在它執(zhí)行之前E2浮)都必須被編譯陋气。所以,JS 編譯器將把程序 var a = 2;
拿過(guò)來(lái)引润,并首先編譯它巩趁,然后準(zhǔn)備運(yùn)行它,通常是立即的淳附。
理解作用域
我們將采用的學(xué)習(xí)作用域的方法晶渠,是將這個(gè)處理過(guò)程想象為一場(chǎng)對(duì)話凰荚。但是,誰(shuí) 在進(jìn)行這場(chǎng)對(duì)話呢褒脯?
演員
讓我們見(jiàn)一見(jiàn)處理程序 var a = 2;
時(shí)進(jìn)行互動(dòng)的演員吧便瑟,這樣我們就能理解稍后將要聽(tīng)到的它們的對(duì)話:
引擎:負(fù)責(zé)從始至終的編譯和執(zhí)行我們的 JavaScript 程序。
編譯器:引擎 的朋友之一番川;處理所有的解析和代碼生成的重活兒(見(jiàn)前一節(jié))到涂。
作用域:引擎 的另一個(gè)朋友;收集并維護(hù)一張所有被聲明的標(biāo)識(shí)符(變量)的列表颁督,并對(duì)當(dāng)前執(zhí)行中的代碼如何訪問(wèn)這些變量強(qiáng)制實(shí)施一組嚴(yán)格的規(guī)則践啄。
為了 全面理解 JavaScript 是如何工作的,你需要開(kāi)始像 引擎(和它的朋友們)那樣 思考沉御,問(wèn)它們問(wèn)的問(wèn)題屿讽,并像它們一樣回答。
反復(fù)
當(dāng)你看到程序 var a = 2;
時(shí)吠裆,你很可能認(rèn)為它是一個(gè)語(yǔ)句伐谈。但這不是我們的新朋友 引擎 所看到的。事實(shí)上试疙,引擎 看到兩個(gè)不同的語(yǔ)句诵棵,一個(gè)是 編譯器 將在編譯期間處理的,一個(gè)是 引擎 將在執(zhí)行期間處理的祝旷。
那么履澳,讓我們來(lái)分析 引擎 和它的朋友們將如何處理程序 var a = 2;
。
編譯器 將對(duì)這個(gè)程序做的第一件事情怀跛,是進(jìn)行詞法分析來(lái)將它分解為一系列 token距贷,然后這些 token 被解析為一棵樹(shù)。但是當(dāng) 編譯器 到了代碼生成階段時(shí)吻谋,它會(huì)以一種與我們可能想象的不同的方式來(lái)對(duì)待這段程序忠蝗。
一個(gè)合理的假設(shè)是,編譯器 將產(chǎn)生的代碼可以用這種假想代碼概括:“為一個(gè)變量分配內(nèi)存滨溉,將它標(biāo)記為 a
什湘,然后將值 2
貼在這個(gè)變量里”长赞。不幸的是晦攒,這不是十分準(zhǔn)確。
編譯器 將會(huì)這樣處理:
遇到
var a
得哆,編譯器 讓 作用域 去查看對(duì)于這個(gè)特定的作用域集合脯颜,變量a
是否已經(jīng)存在了。如果是贩据,編譯器 就忽略這個(gè)聲明并繼續(xù)前進(jìn)栋操。否則闸餐,編譯器 就讓 作用域 去為這個(gè)作用域集合聲明一個(gè)稱為a
的新變量。然后 編譯器 為 引擎 生成稍后要執(zhí)行的代碼矾芙,來(lái)處理賦值
a = 2
舍沙。引擎 運(yùn)行的代碼首先讓 作用域 去查看在當(dāng)前的作用域集合中是否有一個(gè)稱為a
的變量可以訪問(wèn)。如果有剔宪,引擎 就使用這個(gè)變量拂铡。如果沒(méi)有,引擎 就查看 其他地方(參見(jiàn)下面的嵌套 作用域 一節(jié))葱绒。
如果 引擎 最終找到一個(gè)變量感帅,它就將值 2
賦予它。如果沒(méi)有地淀,引擎 將會(huì)舉起它的手并喊出一個(gè)錯(cuò)誤失球!
總結(jié)來(lái)說(shuō):對(duì)于一個(gè)變量賦值,發(fā)生了兩個(gè)不同的動(dòng)作:第一帮毁,編譯器 聲明一個(gè)變量(如果先前沒(méi)有在當(dāng)前作用域中聲明過(guò))实苞,第二,當(dāng)執(zhí)行時(shí)作箍,引擎 在 作用域 中查詢這個(gè)變量并給它賦值硬梁,如果找到的話。
編譯器術(shù)語(yǔ)
為了繼續(xù)更深入地理解胞得,我們需要一點(diǎn)兒更多的編譯器術(shù)語(yǔ)荧止。
當(dāng) 引擎 執(zhí)行 編譯器 在第二步為它產(chǎn)生的代碼時(shí),它必須查詢變量 a
來(lái)看它是否已經(jīng)被聲明過(guò)了阶剑,而且這個(gè)查詢是咨詢 作用域 的跃巡。但是 引擎 所實(shí)施的查詢的類型會(huì)影響查詢的結(jié)果。
在我們這個(gè)例子中牧愁,引擎 將會(huì)對(duì)變量 a
實(shí)施一個(gè)“LHS”查詢素邪。另一種類型的查詢稱為“RHS”。
我打賭你能猜出“L”和“R”是什么意思猪半。這兩個(gè)術(shù)語(yǔ)表示“Left-hand Side(左手邊)”和“Right-hand Side(右手邊)”
什么的……邊兔朦?賦值操作的。
換言之磨确,當(dāng)一個(gè)變量出現(xiàn)在賦值操作的左手邊時(shí)沽甥,會(huì)進(jìn)行 LHS 查詢,當(dāng)一個(gè)變量出現(xiàn)在賦值操作的右手邊時(shí)乏奥,會(huì)進(jìn)行 RHS 查詢摆舟。
實(shí)際上,我們可以表述得更準(zhǔn)確一點(diǎn)兒。對(duì)于我們的目的來(lái)說(shuō)恨诱,一個(gè) RHS 是難以察覺(jué)的媳瞪,因?yàn)樗?jiǎn)單地查詢某個(gè)變量的值,而 LHS 查詢是試著找到變量容器本身照宝,以便它可以賦值蛇受。從這種意義上說(shuō),RHS 的含義實(shí)質(zhì)上不是 真正的 “一個(gè)賦值的右手邊”厕鹃,更準(zhǔn)確地說(shuō)龙巨,它只是意味著“不是左手邊”。
在這一番油腔滑調(diào)之后熊响,你也可以認(rèn)為“RHS”意味著“取得他/她的源(值)”旨别,暗示著 RHS 的意思是“去取……的值”。
讓我們挖掘得更深一些汗茄。
當(dāng)我說(shuō):
console.log( a );
這個(gè)指向 a
的引用是一個(gè) RHS 引用秸弛,因?yàn)檫@里沒(méi)有東西被賦值給 a
。而是我們?cè)诓樵?a
并取得它的值洪碳,這樣這個(gè)值可以被傳遞進(jìn) console.log(..)
递览。
作為對(duì)比:
a = 2;
這里指向 a
的引用是一個(gè) LHS 引用,因?yàn)槲覀儗?shí)際上不關(guān)心當(dāng)前的值是什么瞳腌,我們只是想找到這個(gè)變量绞铃,將它作為 = 2
賦值操作的目標(biāo)。
注意: LHS 和 RHS 意味著“賦值的左/右手邊”未必像字面上那樣意味著“ =
賦值操作符的左/右邊”嫂侍。賦值有幾種其他的發(fā)生形式儿捧,所以最好在概念上將它考慮為:“賦值的目標(biāo)(LHS)”和“賦值的源(RHS)”。
考慮這段程序挑宠,它既有 LHS 引用又有 RHS 引用:
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
調(diào)用 foo(..)
的最后一行作為一個(gè)函數(shù)調(diào)用要求一個(gè)指向 foo
的 RHS 引用菲盾,意味著,“去查詢 foo
的值各淀,并把它交給我”懒鉴。另外,(..)
意味著 foo
的值應(yīng)當(dāng)被執(zhí)行碎浇,所以它最好實(shí)際上是一個(gè)函數(shù)临谱!
這里有一個(gè)微妙但重要的賦值。你發(fā)現(xiàn)了嗎奴璃?
你可能錯(cuò)過(guò)了這個(gè)代碼段隱含的 a = 2
悉默。它發(fā)生在當(dāng)值 2
作為參數(shù)值傳遞給 foo(..)
函數(shù)時(shí),值 2
被賦值 給了參數(shù) a
溺健。為了(隱含地)給參數(shù) a
賦值麦牺,進(jìn)行了一個(gè) LHS 查詢。
這里還有一個(gè) a
的值的 RHS 引用鞭缭,它的結(jié)果值被傳入 console.log(..)
剖膳。console.log(..)
需要一個(gè)引用來(lái)執(zhí)行。它為 console
對(duì)象進(jìn)行一個(gè) RHS 查詢岭辣,然后發(fā)生一個(gè)屬性解析來(lái)看它是否擁有一個(gè)稱為 log
的方法吱晒。
最后,我們可以將這一過(guò)程概念化為沦童,在將值 2
(通過(guò)變量 a
的 RHS 查詢得到的)傳入 log(..)
時(shí)發(fā)生了一次 LHS/RHS 的交換仑濒。在 log(..)
的原生實(shí)現(xiàn)內(nèi)部,我們可以假定它擁有參數(shù)偷遗,其中的第一個(gè)(也許被稱為 arg1
)在 2
被賦值給它之前墩瞳,進(jìn)行了一次 LHS 引用查詢。
注意: 你可能會(huì)試圖將函數(shù)聲明 function foo(a) {...
概念化為一個(gè)普通的變量聲明和賦值氏豌,比如 var foo
和 foo = function(a){...
喉酌。這樣做會(huì)誘使你認(rèn)為函數(shù)聲明涉及了一次 LHS 查詢。
然而泵喘,一個(gè)微妙但重要的不同是泪电,在這種情況下 編譯器 在代碼生成期間同時(shí)處理聲明和值的定義,如此當(dāng) 引擎 執(zhí)行代碼時(shí)纪铺,沒(méi)有必要將一個(gè)函數(shù)值“賦予” foo
相速。因此,將函數(shù)聲明考慮為一個(gè)我們?cè)谶@里討論的 LHS 查詢賦值是不太合適的鲜锚。
引擎/作用域?qū)υ?/h3>
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
讓我們將上面的(處理這個(gè)代碼段的)交互想象為一場(chǎng)對(duì)話突诬。這場(chǎng)對(duì)話將會(huì)有點(diǎn)兒像這樣進(jìn)行:
引擎:嘿 作用域,我有一個(gè)
foo
的 RHS 引用芜繁。聽(tīng)說(shuō)過(guò)它嗎攒霹?
作用域;啊浆洗,是的催束,聽(tīng)說(shuō)過(guò)。編譯器 剛在一秒鐘之前聲明了它伏社。它是一個(gè)函數(shù)抠刺。給你。
引擎:太棒了摘昌,謝謝速妖!好的,我要執(zhí)行
foo
了聪黎。
引擎:嘿罕容,作用域,我得到了一個(gè)
a
的 LHS 引用,聽(tīng)說(shuō)過(guò)它嗎锦秒?
作用域:啊露泊,是的,聽(tīng)說(shuō)過(guò)旅择。編譯器 剛才將它聲明為
foo
的一個(gè)正式參數(shù)了惭笑。給你。
引擎:一如既往的給力生真,作用域沉噩。再次感謝你。現(xiàn)在柱蟀,該把
2
賦值給a
了川蒙。
引擎:嘿,作用域长已,很抱歉又一次打擾你派歌。我需要 RHS 查詢
console
。聽(tīng)說(shuō)過(guò)它嗎痰哨?
作用域:沒(méi)關(guān)系胶果,引擎,這是我一天到晚的工作斤斧。是的早抠,我得到
console
了。它是一個(gè)內(nèi)建對(duì)象撬讽。給你蕊连。
引擎:完美。查找
log(..)
游昼。好的甘苍,很好,它是一個(gè)函數(shù)烘豌。
引擎:嘿载庭,作用域。你能幫我查一下
a
的 RHS 引用嗎廊佩?我想我記得它囚聚,但只是想再次確認(rèn)一下。
作用域:你是對(duì)的标锄,引擎顽铸。同一個(gè)家伙,沒(méi)變料皇。給你谓松。
引擎:酷星压。傳遞
a
的值,也就是2
鬼譬,給log(..)
娜膘。
...
小測(cè)驗(yàn)
檢查你到目前為止的理解。確保你扮演 引擎拧簸,并與 作用域 “對(duì)話”:
function foo(a) {
var b = a;
return a + b;
}
var c = foo( 2 );
找到所有的 LHS 查詢(有3處!)男窟。
找到所有的 RHS 查詢(有4處E璩唷)导而。
注意: 小測(cè)驗(yàn)答案參見(jiàn)本章的復(fù)習(xí)部分酥夭!
嵌套的作用域
我們說(shuō)過(guò) 作用域 是通過(guò)標(biāo)識(shí)符名稱查詢變量的一組規(guī)則穗熬。但是晨逝,通常會(huì)有多于一個(gè)的 作用域 需要考慮介粘。
就像一個(gè)代碼塊兒或函數(shù)被嵌套在另一個(gè)代碼塊兒或函數(shù)中一樣坪蚁,作用域被嵌套在其他的作用域中走搁。所以建邓,如果在直接作用域中找不到一個(gè)變量的話扇住,引擎 就會(huì)咨詢下一個(gè)外層作用域春缕,如此繼續(xù)直到找到這個(gè)變量或者到達(dá)最外層作用域(也就是全局作用域)。
考慮這段代碼:
function foo(a) {
console.log( a + b );
}
var b = 2;
foo( 2 ); // 4
b
的 RHS 引用不能在函數(shù) foo
的內(nèi)部被解析艘蹋,但是可以在它的外圍 作用域(這個(gè)例子中是全局作用域)中解析锄贼。
所以,重返 引擎 和 作用域 的對(duì)話女阀,我們會(huì)聽(tīng)到:
引擎:“嘿宅荤,
foo
的 作用域,聽(tīng)說(shuō)過(guò)b
嗎浸策?我得到一個(gè)它的 RHS 引用冯键。”
作用域:“沒(méi)有庸汗,從沒(méi)聽(tīng)說(shuō)過(guò)惫确。問(wèn)問(wèn)別人吧◎遣眨”
引擎:“嘿雕薪,
foo
外面的 作用域,哦晓淀,你是全局 作用域所袁,好吧,酷凶掰。聽(tīng)說(shuō)過(guò)b
嗎燥爷?我得到一個(gè)它的 RHS 引用蜈亩。”
作用域:“是的前翎,當(dāng)然有稚配。給你「刍”
遍歷嵌套 作用域 的簡(jiǎn)單規(guī)則:引擎 從當(dāng)前執(zhí)行的 作用域 開(kāi)始道川,在那里查找變量,如果沒(méi)有找到立宜,就向上走一級(jí)繼續(xù)查找冒萄,如此類推。如果到了最外層的全局作用域橙数,那么查找就會(huì)停止尊流,無(wú)論它是否找到了變量。
建筑的隱喻
為了將嵌套 作用域 解析的過(guò)程可視化灯帮,我想讓你考慮一下這個(gè)高層建筑崖技。
這個(gè)建筑物表示我們程序的嵌套 作用域 規(guī)則集合。無(wú)論你在哪里钟哥,建筑的第一層表示你當(dāng)前執(zhí)行的 作用域迎献。建筑的頂層表示全局 作用域。
你通過(guò)在你當(dāng)前的樓層中查找來(lái)解析 LHS 和 RHS 引用腻贰,如果你沒(méi)有找到它忿晕,就坐電梯到上一層樓,在那里尋找银受,然后再上一層践盼,如此類推。一旦你到了頂層(全局 作用域)宾巍,你要么找到了你想要的東西咕幻,要么沒(méi)有。但是不管怎樣你都不得不停止了顶霞。
錯(cuò)誤
為什么我們區(qū)別 LHS 和 RHS 那么重要肄程?
因?yàn)樵谧兞窟€沒(méi)有被聲明(在所有被查詢的 作用域 中都沒(méi)找到)的情況下,這兩種類型的查詢的行為不同选浑。
考慮如下代碼:
function foo(a) {
console.log( a + b );
b = a;
}
foo( 2 );
當(dāng) b
的 RHS 查詢第一次發(fā)生時(shí)蓝厌,它是找不到的。它被說(shuō)成是一個(gè)“未聲明”的變量古徒,因?yàn)樗谧饔糜蛑姓也坏健?/p>
如果 RHS 查詢?cè)谇短椎?作用域 的任何地方都找不到一個(gè)值拓提,這會(huì)導(dǎo)致 引擎 拋出一個(gè) ReferenceError
。必須要注意的是這個(gè)錯(cuò)誤的類型是 ReferenceError
隧膘。
相比之下代态,如果 引擎 在進(jìn)行一個(gè) LHS 查詢寺惫,但到達(dá)了頂層(全局 作用域)都沒(méi)有找到它,而且如果程序沒(méi)有運(yùn)行在“Strict模式”[1]下蹦疑,那么這個(gè)全局 作用域 將會(huì)在 全局作用域中 創(chuàng)建一個(gè)同名的新變量西雀,并把它交還給 引擎。
“不歉摧,之前沒(méi)有這樣的東西艇肴,但是我可以幫忙給你創(chuàng)建一個(gè)∪拢”
在 ES5 中被加入的“Strict模式”[1]再悼,有許多與一般/寬松/懶惰模式不同的行為。其中之一就是不允許自動(dòng)/隱含的全局變量創(chuàng)建券盅。在這種情況下帮哈,將不會(huì)有全局 作用域 的變量交回給 LHS 查詢膛檀,并且類似于 RHS 的情況, 引擎 將拋出一個(gè) ReferenceError
锰镀。
現(xiàn)在,如果一個(gè) RHS 查詢的變量被找到了咖刃,但是你試著去做一些這個(gè)值不可能做到的事泳炉,比如將一個(gè)非函數(shù)的值作為函數(shù)運(yùn)行,或者引用 null
或者 undefined
值的屬性嚎杨,那么 引擎 就會(huì)拋出一個(gè)不同種類的錯(cuò)誤花鹅,稱為 TypeError
。
ReferenceError
是關(guān)于 作用域 解析失敗的枫浙,而 TypeError
暗示著 作用域 解析成功了刨肃,但是試圖對(duì)這個(gè)結(jié)果進(jìn)行了一個(gè)非法/不可能的動(dòng)作。
復(fù)習(xí)
作用域是一組規(guī)則箩帚,它決定了一個(gè)變量(標(biāo)識(shí)符)在哪里和如何被查找真友。這種查詢也許是為了向這個(gè)變量賦值,這時(shí)變量是一個(gè) LHS(左手邊)引用紧帕,或者是為取得它的值盔然,這時(shí)變量是一個(gè) RHS(右手邊)引用。
LHS 引用得自賦值操作是嗜。作用域 相關(guān)的賦值可以通過(guò) =
操作符發(fā)生愈案,也可以通過(guò)向函數(shù)參數(shù)傳遞(賦予)參數(shù)值發(fā)生。
JavaScript 引擎 在執(zhí)行代碼之前首先會(huì)編譯它鹅搪,因此站绪,它將 var a = 2;
這樣的語(yǔ)句分割為兩個(gè)分離的步驟:
首先,
var a
在當(dāng)前 作用域 中聲明丽柿。這是在最開(kāi)始崇众,代碼執(zhí)行之前實(shí)施的掂僵。稍后,
a = 2
查找這個(gè)變量(LHS 引用)顷歌,并且如果找到就向它賦值锰蓬。
LHS 和 RHS 引用查詢都從當(dāng)前執(zhí)行中的 作用域 開(kāi)始,如果有需要(也就是眯漩,它們?cè)谶@里沒(méi)能找到它們要找的東西)芹扭,它們會(huì)在嵌套的 作用域 中一路向上,一次一個(gè)作用域(層)地查找這個(gè)標(biāo)識(shí)符赦抖,直到它們到達(dá)全局作用域(頂層)并停止舱卡,既可能找到也可能沒(méi)找到。
未被滿足的 RHS 引用會(huì)導(dǎo)致 ReferenceError
被拋出队萤。未被滿足的 LHS 引用會(huì)導(dǎo)致一個(gè)自動(dòng)的轮锥,隱含地創(chuàng)建的同名全局變量(如果不是“Strict模式”[1]),或者一個(gè) ReferenceError
(如果是“Strict模式”[1])要尔。
小測(cè)驗(yàn)答案
function foo(a) {
var b = a;
return a + b;
}
var c = foo( 2 );
-
找出所有的 LHS 查詢(有3處I岫拧)。
c = ..
,a = 2
(隱含的參數(shù)賦值)和b = ..
-
找出所有的 RHS 查詢(有4處U栽)既绩。
foo(2..
,= a;
,a + ..
和.. + b
-
MDN: Strict Mode ? ? ? ?