第一章:什么是作用域淀弹?

特別說(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è)步驟旗笔,大致被稱為“編譯”:

  1. 分詞/詞法分析: 將一連串字符打斷成(對(duì)于語(yǔ)言來(lái)說(shuō))有意義的片段彪置,稱為 token(記號(hào))。舉例來(lái)說(shuō)蝇恶,考慮這段程序:var a = 2;拳魁。這段程序很可能會(huì)被打斷成如下 token:vara撮弧,=潘懊,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 的一部分署浩,那么這就是 詞法分析揉燃。

  2. 解析: 將一個(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)。

  3. 代碼生成: 這個(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ì)話:

  1. 引擎:負(fù)責(zé)從始至終的編譯和執(zhí)行我們的 JavaScript 程序。

  2. 編譯器引擎 的朋友之一番川;處理所有的解析和代碼生成的重活兒(見(jiàn)前一節(jié))到涂。

  3. 作用域引擎 的另一個(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ì)這樣處理:

  1. 遇到 var a得哆,編譯器作用域 去查看對(duì)于這個(gè)特定的作用域集合脯颜,變量 a 是否已經(jīng)存在了。如果是贩据,編譯器 就忽略這個(gè)聲明并繼續(xù)前進(jìn)栋操。否則闸餐,編譯器 就讓 作用域 去為這個(gè)作用域集合聲明一個(gè)稱為 a 的新變量。

  2. 然后 編譯器引擎 生成稍后要執(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 foofoo = 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 );

讓我們將上面的(處理這個(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 );
  1. 找到所有的 LHS 查詢(有3處!)男窟。

  2. 找到所有的 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è)分離的步驟:

  1. 首先,var a 在當(dāng)前 作用域 中聲明丽柿。這是在最開(kāi)始崇众,代碼執(zhí)行之前實(shí)施的掂僵。

  2. 稍后,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 );
  1. 找出所有的 LHS 查詢(有3處I岫拧)。

    c = .., a = 2(隱含的參數(shù)賦值)和 b = ..

  2. 找出所有的 RHS 查詢(有4處U栽)既绩。

    foo(2.., = a;, a + .... + b


  1. MDN: Strict Mode ? ? ? ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市还惠,隨后出現(xiàn)的幾起案子饲握,更是在濱河造成了極大的恐慌,老刑警劉巖蚕键,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件救欧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锣光,警方通過(guò)查閱死者的電腦和手機(jī)笆怠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嫉晶,“玉大人骑疆,你說(shuō)我怎么就攤上這事√娣希” “怎么了箍铭?”我有些...
    開(kāi)封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)椎镣。 經(jīng)常有香客問(wèn)我诈火,道長(zhǎng),這世上最難降的妖魔是什么状答? 我笑而不...
    開(kāi)封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任冷守,我火速辦了婚禮刀崖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拍摇。我一直安慰自己亮钦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布充活。 她就那樣靜靜地躺著蜂莉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪混卵。 梳的紋絲不亂的頭發(fā)上映穗,一...
    開(kāi)封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音幕随,去河邊找鬼蚁滋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛赘淮,可吹牛的內(nèi)容都是我干的辕录。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拥知,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踏拜!你這毒婦竟也來(lái)了碎赢?” 一聲冷哼從身側(cè)響起低剔,我...
    開(kāi)封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肮塞,沒(méi)想到半個(gè)月后襟齿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枕赵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年猜欺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拷窜。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡开皿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出篮昧,到底是詐尸還是另有隱情赋荆,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布懊昨,位于F島的核電站窄潭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏酵颁。R本人自食惡果不足惜嫉你,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一月帝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧幽污,春花似錦嚷辅、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至深寥,卻和暖如春攘乒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惋鹅。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工则酝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闰集。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓沽讹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親武鲁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子爽雄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容