你不懂JS:作用域與閉包

你不懂JS:作用域與閉包

第一章:什么是作用域?

幾乎所有語(yǔ)言的最基礎(chǔ)模型之一就是在變量中存儲(chǔ)值廊驼,并且在稍后取出或修改這些值的能力据过。事實(shí)上惋砂,在變量中存儲(chǔ)值和取出值的能力,給程序賦予了 狀態(tài)绳锅。

如果沒(méi)有這樣的概念西饵,一個(gè)程序雖然可以執(zhí)行一些任務(wù),但是它們將會(huì)受到極大的限制而且不會(huì)非常有趣榨呆。

但是在我們的程序中納入變量罗标,引出了我們現(xiàn)在將要解決的最有趣的問(wèn)題:這些變量 存活 在哪里?換句話(huà)說(shuō)积蜻,它們被存儲(chǔ)在哪兒闯割?而且,最重要的是竿拆,我們的程序如何在需要它們的時(shí)候找到它們宙拉?

回答這些問(wèn)題需要一組明確定義的規(guī)則,它定義如何在某些位置存儲(chǔ)變量丙笋,以及如何在稍后找到這些變量谢澈。我們稱(chēng)這組規(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è)步驟何陆,大致被稱(chēng)為“編譯”:

  1. 分詞/詞法分析: 將一連串字符打斷成(對(duì)于語(yǔ)言來(lái)說(shuō))有意義的片段,稱(chēng)為 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 的一部分并思,那么這就是 詞法分析庐氮。

  2. 解析: 將一個(gè) token 的流(數(shù)組)轉(zhuǎn)換為一個(gè)嵌套元素的樹(shù),它綜合地表示了程序的語(yǔ)法結(jié)構(gòu)。這棵樹(shù)稱(chēng)為“抽象語(yǔ)法樹(shù)”(AST —— <b>A</b>bstract <b>S</b>yntax <b>T</b>ree)。

    var a = 2; 的樹(shù)也許開(kāi)始于稱(chēng)為 VariableDeclaration(變量聲明)頂層節(jié)點(diǎn)羹与,帶有一個(gè)稱(chēng)為 Identifier(標(biāo)識(shí)符)的子節(jié)點(diǎn)(它的值為 a)账胧,和另一個(gè)稱(chēng)為 AssignmentExpression(賦值表達(dá)式)的子節(jié)點(diǎn),而這個(gè)子節(jié)點(diǎn)本身帶有一個(gè)稱(chēng)為 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è)稱(chēng)為 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)(或更少!)院峡。為了確保最快的性能兴使,JS 引擎將使用所有的招數(shù)(比如 JIT,它可以懶編譯甚至是熱編譯撕予,等等)鲫惶,而這遠(yuǎn)超出了我們關(guān)于“作用域”的討論。

為了簡(jiǎn)單起見(jiàn)实抡,我們可以說(shuō)欠母,任何 JavaScript 代碼段在它執(zhí)行之前(通常是 剛好 在它執(zhí)行之前!)都必須被編譯吆寨。所以赏淌,JS 編譯器將把程序 var a = 2; 拿過(guò)來(lái),并首先編譯它啄清,然后準(zhǔn)備運(yùn)行它六水,通常是立即的。

理解作用域

我們將采用的學(xué)習(xí)作用域的方法辣卒,是將這個(gè)處理過(guò)程想象為一場(chǎng)對(duì)話(huà)掷贾。但是,誰(shuí) 在進(jìn)行這場(chǎng)對(duì)話(huà)呢荣茫?

演員

讓我們見(jiàn)一見(jiàn)處理程序 var a = 2; 時(shí)進(jìn)行互動(dòng)的演員吧想帅,這樣我們就能理解稍后將要聽(tīng)到的它們的對(duì)話(huà):

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

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

  3. 作用域引擎 的另一個(gè)朋友;收集并維護(hù)一張所有被聲明的標(biāo)識(shí)符(變量)的列表咧欣,并對(duì)當(dāng)前執(zhí)行中的代碼如何訪(fǎng)問(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è)稱(chēng)為 a 的新變量。

  2. 然后 編譯器引擎 生成稍后要執(zhí)行的代碼蛔钙,來(lái)處理賦值 a = 2锌云。引擎 運(yùn)行的代碼首先讓 作用域 去查看在當(dāng)前的作用域集合中是否有一個(gè)稱(chēng)為 a 的變量可以訪(fǎng)問(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í),引擎作用域 中查詢(xún)這個(gè)變量并給它賦值枷畏,如果找到的話(huà)别厘。

編譯器術(shù)語(yǔ)

為了繼續(xù)更深入地理解,我們需要一點(diǎn)兒更多的編譯器術(shù)語(yǔ)拥诡。

當(dāng) 引擎 執(zhí)行 編譯器 在第二步為它產(chǎn)生的代碼時(shí)触趴,它必須查詢(xún)變量 a 來(lái)看它是否已經(jīng)被聲明過(guò)了,而且這個(gè)查詢(xún)是咨詢(xún) 作用域 的渴肉。但是 引擎 所實(shí)施的查詢(xún)的類(lèi)型會(huì)影響查詢(xún)的結(jié)果冗懦。

在我們這個(gè)例子中,引擎 將會(huì)對(duì)變量 a 實(shí)施一個(gè)“LHS”查詢(xún)仇祭。另一種類(lèi)型的查詢(xún)稱(chēng)為“RHS”披蕉。

我打賭你能猜出“L”和“R”是什么意思。這兩個(gè)術(shù)語(yǔ)表示“Left-hand Side(左手邊)”和“Right-hand Side(右手邊)”

什么的……邊乌奇?賦值操作的没讲。

換言之,當(dāng)一個(gè)變量出現(xiàn)在賦值操作的左手邊時(shí)礁苗,會(huì)進(jìn)行 LHS 查詢(xún)食零,當(dāng)一個(gè)變量出現(xiàn)在賦值操作的右手邊時(shí),會(huì)進(jìn)行 RHS 查詢(xún)寂屏。

實(shí)際上贰谣,我們可以表述得更準(zhǔn)確一點(diǎn)兒。對(duì)于我們的目的來(lái)說(shuō)迁霎,一個(gè) RHS 是難以察覺(jué)的吱抚,因?yàn)樗?jiǎn)單地查詢(xún)某個(gè)變量的值,而 LHS 查詢(xún)是試著找到變量容器本身考廉,以便它可以賦值秘豹。從這種意義上說(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è)诓樵?xún) 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 引用淆衷,意味著,“去查詢(xún) 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 查詢(xún)。

這里還有一個(gè) a 的值的 RHS 引用枝哄,它的結(jié)果值被傳入 console.log(..)肄梨。console.log(..) 需要一個(gè)引用來(lái)執(zhí)行。它為 console 對(duì)象進(jìn)行一個(gè) RHS 查詢(xún)挠锥,然后發(fā)生一個(gè)屬性解析來(lái)看它是否擁有一個(gè)稱(chēng)為 log 的方法众羡。

最后,我們可以將這一過(guò)程概念化為蓖租,在將值 2(通過(guò)變量 a 的 RHS 查詢(xún)得到的)傳入 log(..) 時(shí)發(fā)生了一次 LHS/RHS 的交換粱侣。在 log(..) 的原生實(shí)現(xiàn)內(nèi)部,我們可以假定它擁有參數(shù)蓖宦,其中的第一個(gè)(也許被稱(chēng)為 arg1)在 2 被賦值給它之前齐婴,進(jìn)行了一次 LHS 引用查詢(xún)。

注意: 你可能會(huì)試圖將函數(shù)聲明 function foo(a) {... 概念化為一個(gè)普通的變量聲明和賦值稠茂,比如 var foofoo = function(a){...尔店。這樣做會(huì)誘使你認(rèn)為函數(shù)聲明涉及了一次 LHS 查詢(xún)。

然而,一個(gè)微妙但重要的不同是嚣州,在這種情況下 編譯器 在代碼生成期間同時(shí)處理聲明和值的定義鲫售,如此當(dāng) 引擎 執(zhí)行代碼時(shí),沒(méi)有必要將一個(gè)函數(shù)值“賦予” foo该肴。因此情竹,將函數(shù)聲明考慮為一個(gè)我們?cè)谶@里討論的 LHS 查詢(xún)賦值是不太合適的。

引擎/作用域?qū)υ?huà)

function foo(a) {
    console.log( a ); // 2
}

foo( 2 );

讓我們將上面的(處理這個(gè)代碼段的)交互想象為一場(chǎng)對(duì)話(huà)匀哄。這場(chǎng)對(duì)話(huà)將會(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 查詢(xún) 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ì)話(huà)”:

function foo(a) {
    var b = a;
    return a + b;
}

var c = foo( 2 );
  1. 找到所有的 LHS 查詢(xún)(有3處F奂健)树绩。

  2. 找到所有的 RHS 查詢(xún)(有4處!)隐轩。

注意: 小測(cè)驗(yàn)答案參見(jiàn)本章的復(fù)習(xí)部分饺饭!

嵌套的作用域

我們說(shuō)過(guò) 作用域 是通過(guò)標(biāo)識(shí)符名稱(chēng)查詢(xún)變量的一組規(guī)則。但是职车,通常會(huì)有多于一個(gè)的 作用域 需要考慮瘫俊。

就像一個(gè)代碼塊兒或函數(shù)被嵌套在另一個(gè)代碼塊兒或函數(shù)中一樣鹊杖,作用域被嵌套在其他的作用域中。所以扛芽,如果在直接作用域中找不到一個(gè)變量的話(huà)骂蓖,引擎 就會(huì)咨詢(xún)下一個(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à)庐船,我們會(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ù)查找透揣,如此類(lèi)推济炎。如果到了最外層的全局作用域,那么查找就會(huì)停止辐真,無(wú)論它是否找到了變量须尚。

建筑的隱喻

為了將嵌套 作用域 解析的過(guò)程可視化崖堤,我想讓你考慮一下這個(gè)高層建筑。

<img src="fig1.png" width="250">

這個(gè)建筑物表示我們程序的嵌套 作用域 規(guī)則集合耐床。無(wú)論你在哪里密幔,建筑的第一層表示你當(dāng)前執(zhí)行的 作用域。建筑的頂層表示全局 作用域撩轰。

你通過(guò)在你當(dāng)前的樓層中查找來(lái)解析 LHS 和 RHS 引用胯甩,如果你沒(méi)有找到它,就坐電梯到上一層樓堪嫂,在那里尋找偎箫,然后再上一層,如此類(lèi)推皆串。一旦你到了頂層(全局 作用域)淹办,你要么找到了你想要的東西,要么沒(méi)有恶复。但是不管怎樣你都不得不停止了怜森。

錯(cuò)誤

為什么我們區(qū)別 LHS 和 RHS 那么重要?

因?yàn)樵谧兞窟€沒(méi)有被聲明(在所有被查詢(xún)的 作用域 中都沒(méi)找到)的情況下谤牡,這兩種類(lèi)型的查詢(xún)的行為不同副硅。

考慮如下代碼:

function foo(a) {
    console.log( a + b );
    b = a;
}

foo( 2 );

當(dāng) b 的 RHS 查詢(xún)第一次發(fā)生時(shí),它是找不到的翅萤。它被說(shuō)成是一個(gè)“未聲明”的變量恐疲,因?yàn)樗谧饔糜蛑姓也坏健?/p>

如果 RHS 查詢(xún)?cè)谇短椎?作用域 的任何地方都找不到一個(gè)值,這會(huì)導(dǎo)致 引擎 拋出一個(gè) ReferenceError断序。必須要注意的是這個(gè)錯(cuò)誤的類(lèi)型是 ReferenceError流纹。

相比之下糜烹,如果 引擎 在進(jìn)行一個(gè) LHS 查詢(xún)违诗,但到達(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 查詢(xún)囊陡,并且類(lèi)似于 RHS 的情況, 引擎 將拋出一個(gè) ReferenceError

現(xiàn)在掀亥,如果一個(gè) RHS 查詢(xún)的變量被找到了撞反,但是你試著去做一些這個(gè)值不可能做到的事,比如將一個(gè)非函數(shù)的值作為函數(shù)運(yùn)行搪花,或者引用 null 或者 undefined 值的屬性遏片,那么 引擎 就會(huì)拋出一個(gè)不同種類(lèi)的錯(cuò)誤,稱(chēng)為 TypeError撮竿。

ReferenceError 是關(guān)于 作用域 解析失敗的吮便,而 TypeError 暗示著 作用域 解析成功了,但是試圖對(duì)這個(gè)結(jié)果進(jìn)行了一個(gè)非法/不可能的動(dòng)作倚聚。

復(fù)習(xí)

作用域是一組規(guī)則线衫,它決定了一個(gè)變量(標(biāo)識(shí)符)在哪里和如何被查找。這種查詢(xún)也許是為了向這個(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 引用查詢(xún)都從當(dāng)前執(zhí)行中的 作用域 開(kāi)始设联,如果有需要(也就是,它們?cè)谶@里沒(méi)能找到它們要找的東西)灼捂,它們會(huì)在嵌套的 作用域 中一路向上离例,一次一個(gè)作用域(層)地查找這個(gè)標(biāo)識(shí)符,直到它們到達(dá)全局作用域(頂層)并停止悉稠,既可能找到也可能沒(méi)找到宫蛆。

未被滿(mǎn)足的 RHS 引用會(huì)導(dǎo)致 ReferenceError 被拋出。未被滿(mǎn)足的 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 查詢(xún)(有3處!)袍冷。

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

  2. 找出所有的 RHS 查詢(xún)(有4處A状住)。

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


  1. MDN: Strict Mode ? ? ? ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胡诗,一起剝皮案震驚了整個(gè)濱河市邓线,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌煌恢,老刑警劉巖骇陈,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瑰抵,居然都是意外死亡你雌,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)二汛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)婿崭,“玉大人,你說(shuō)我怎么就攤上這事肴颊∶フ唬” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵婿着,是天一觀(guān)的道長(zhǎng)授瘦。 經(jīng)常有香客問(wèn)我,道長(zhǎng)竟宋,這世上最難降的妖魔是什么提完? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮丘侠,結(jié)果婚禮上徒欣,老公的妹妹穿的比我還像新娘。我一直安慰自己婉陷,他們只是感情好帚称,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布官研。 她就那樣靜靜地躺著秽澳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戏羽。 梳的紋絲不亂的頭發(fā)上担神,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音始花,去河邊找鬼妄讯。 笑死孩锡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亥贸。 我是一名探鬼主播躬窜,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炕置!你這毒婦竟也來(lái)了荣挨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤朴摊,失蹤者是張志新(化名)和其女友劉穎默垄,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體甚纲,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡口锭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了介杆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹃操。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖春哨,靈堂內(nèi)的尸體忽然破棺而出组民,到底是詐尸還是另有隱情,我是刑警寧澤悲靴,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布臭胜,位于F島的核電站,受9級(jí)特大地震影響癞尚,放射性物質(zhì)發(fā)生泄漏耸三。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一浇揩、第九天 我趴在偏房一處隱蔽的房頂上張望仪壮。 院中可真熱鬧,春花似錦胳徽、人聲如沸积锅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缚陷。三九已至,卻和暖如春往核,著一層夾襖步出監(jiān)牢的瞬間箫爷,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虎锚,地道東北人硫痰。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像窜护,于是被迫代替她去往敵國(guó)和親效斑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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