如果我的文章對你有用屁商,請給我一個贊烟很,讓我有繼續(xù)堅持的動力/微笑。
原創(chuàng)文章蜡镶,此文章僅供學(xué)習(xí)參考使用镜撩,歡迎訪問我的個人網(wǎng)站zhengyepan
一葛作、理解作用域
js越是基礎(chǔ)的知識,越是會被人以為沒有什么大不了的讥耗,其實毒坛,js的基礎(chǔ)是很有“內(nèi)涵”的望伦,就作用域來講林说。首先需要了解幾個概念
1、引擎:從頭到尾負(fù)責(zé)JavaScript的編譯和執(zhí)行過程.
2屯伞、編譯器:負(fù)責(zé)語法分析與代碼生成腿箩。
3、作用域:負(fù)責(zé)聲明并維護由所有聲明的標(biāo)識符(變量)組成一系列查詢劣摇,并實施一套非常嚴(yán)格的規(guī)則珠移,確定當(dāng)前執(zhí)行的代碼對這些標(biāo)識符的訪問權(quán)限。
變量的賦值操作會執(zhí)行兩個階段末融,首先編譯器會在當(dāng)前作用域下聲明一個變量(如果之前沒有聲明過)钧惧,然后在運行時引擎會在作用域中查找該變量,如果能夠找到就會對它賦值勾习。
編譯器在編譯過程中的第二步生成代碼浓瞪,引擎執(zhí)行到它時,會通過查找變量a來判斷是是否被聲明過巧婶。查找的過程有作用域進行協(xié)助乾颁,但是引擎執(zhí)行怎么樣的查找會影響最終的查找結(jié)果。
引擎查找的方式有LHS和RHS艺栈∮⒘耄“L”和“R”分別代表著左側(cè)和右側(cè),具體是一個賦值操作的左側(cè)和右側(cè)眼滤。
那么引擎什么時候進行LHS或者RHS查找方式呢巴席?答案就是當(dāng)變量出現(xiàn)在賦值操作左側(cè)的時候進行LHS,當(dāng)變量出現(xiàn)在賦值操作右側(cè)的時候進行RHS。更加準(zhǔn)確的來講诅需,RHS的真正意思是“取到它的源值”漾唉,這意味著得到某某的值,其中對a 的引用是一個RHS 引用堰塌,因為這里a 并沒有賦予任何值赵刑。相應(yīng)地,需要查找并取得a 的值场刑,這樣才能將值傳遞給console.log(..)般此。相比之下,例如:a = 2;這里對a 的引用則是LHS 引用牵现,因為實際上我們并不關(guān)心當(dāng)前的值是什么铐懊,只是想要為=2 這個賦值操作找到一個目標(biāo)。
重要結(jié)論:LHS 和RHS 的含義是“賦值操作的左側(cè)或右側(cè)”并不一定意味著就是“=賦值操作符的左側(cè)或右側(cè)”瞎疼。賦值操作還有其他幾種形式科乎,因此在概念上最好將其理解為“賦值操作的目標(biāo)是誰(LHS)”以及“誰是賦值操作的源頭RHS)”。
下面的程序贼急,其中既有LHS 也有RHS 引用:
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
最后一行foo(..) 函數(shù)的調(diào)用需要對foo 進行RHS 引用茅茂,意味著“去找到foo 的值捏萍,并把它給我”。并且(..) 意味著foo 的值需要被執(zhí)行空闲,因此它最好真的是一個函數(shù)類型的值令杈!這里還有一個容易被忽略卻非常重要的細(xì)節(jié)。代碼中隱式的a=2 操作可能很容易被你忽略掉碴倾。這個操作發(fā)生在2 被當(dāng)作參數(shù)傳遞給foo(..) 函數(shù)時逗噩,2 會被分配給參數(shù)a。為了給參數(shù)a(隱式地)分配值影斑,需要進行一次LHS 查詢给赞。
這里還有對a 進行的RHS 引用, 并且將得到的值傳給了console.log(..)矫户。console.log(..) 本身也需要一個引用才能執(zhí)行片迅,因此會對console 對象進行RHS 查詢,并且檢查得到的值中是否有一個叫作log 的方法皆辽。最后柑蛇,在概念上可以理解為在LHS 和RHS 之間通過對值2 進行交互來將其傳遞進log(..)(通過變量a 的RHS 查詢)。假設(shè)在log(..) 函數(shù)的原生實現(xiàn)中它可以接受參數(shù)驱闷,在將2 賦值給其中第一個(也許叫作arg1)參數(shù)之前耻台,這個參數(shù)需要進行LHS 引用查詢。
有可能你可能會傾向于將函數(shù)聲明function foo(a) {... 概念化為普通的變量聲明和賦值空另,比如var foo盆耽、foo = function(a) {...。如果這樣理解的話扼菠,這個函數(shù)聲明將需要進行LHS 查詢摄杂。然而還有一個重要的細(xì)微差別,編譯器可以在代碼生成的同時處理聲明和值的定義循榆,比如在引擎執(zhí)行代碼時析恢,并不會有線程專門用來將一個函數(shù)值“分配給”foo。因此秧饮,將函數(shù)聲明理解成前面討論的LHS 查詢和賦值的形式并不合適映挂。
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
讓我們來模擬上面過程中引擎和作用域之前的操作:
引擎需要為foo 進行RHS 引用。于是向作用域請求foo的聲明
作用域響應(yīng):編譯器剛剛聲明了它盗尸。它是一個函數(shù)柑船,給你。
引擎:執(zhí)行foo泼各。
引擎:需要為a 進行LHS 引用椎组,向作用域請求
作用域:編譯器最近把它聲名為foo 的一個形式參數(shù)了,給你历恐。
引擎:收到a ,現(xiàn)在要把2 賦值給a寸癌。
引擎:要為console 進行RHS 引用,問作用域見過它嗎弱贼?
作用域:console 是個內(nèi)置對象蒸苇。給引擎。
引擎:這里面是不是有l(wèi)og(..)吮旅。太好了溪烤,找到了,是一個函數(shù)庇勃。
引擎:請問a 的RHS 引用嗎檬嘀?雖然我記得它,但想再確認(rèn)一次责嚷。
作用域:這個變量沒有變動過鸳兽,拿走,不謝罕拂。
引擎:真棒揍异。我來把a 的值,也就是2爆班,傳遞進log(..)衷掷。
……
為什么區(qū)分LHS 和RHS 是一件重要的事情?因為在變量還沒有聲明(在任何作用域中都無法找到該變量)的情況下柿菩,這兩種查詢的行為是不一樣的戚嗅。
舉個例子:
function foo(a) {
console.log( a + b );
b = a;
}
foo( 2 );
第一次對b 進行RHS 查詢時是無法找到該變量的。也就是說枢舶,這是一個“未聲明”的變量懦胞,因為在任何相關(guān)的作用域中都無法找到它。如果RHS 查詢在所有嵌套的作用域中遍尋不到所需的變量祟辟,引擎就會拋出ReferenceError異常医瘫。
值得注意的是,ReferenceError 是非常重要的異常類型旧困。相較之下醇份,當(dāng)引擎執(zhí)行LHS 查詢時,如果在頂層(全局作用域)中也無法找到目標(biāo)變量吼具,全局作用域中就會創(chuàng)建一個具有該名稱的變量僚纷,并將其返還給引擎,前提是程序運行在非“嚴(yán)格模式”下拗盒〔澜撸“不,這個變量之前并不存在陡蝇,但是我很熱心地幫你創(chuàng)建了一個痊臭。
”ES5 中引入了“嚴(yán)格模式”哮肚。同正常模式,或者說寬松/ 懶惰模式相比广匙,嚴(yán)格模式在行為上有很多不同允趟。其中一個不同的行為是嚴(yán)格模式禁止自動或隱式地創(chuàng)建全局變量。
因此鸦致,在嚴(yán)格模式中LHS 查詢失敗時潮剪,并不會創(chuàng)建并返回一個全局變量,引擎會拋出同RHS 查詢失敗時類似的ReferenceError 異常分唾。接下來抗碰,如果RHS 查詢找到了一個變量,但是你嘗試對這個變量的值進行不合理的操作绽乔,比如試圖對一個非函數(shù)類型的值進行函數(shù)調(diào)用弧蝇,或著引用null 或undefined 類型的值中的屬性,那么引擎會拋出另外一種類型的異常迄汛,叫作TypeError捍壤。
ReferenceError 同作用域判別失敗相關(guān),而TypeError 則代表作用域判別成功了鞍爱,但是對結(jié)果的操作是非法或不合理的鹃觉。
二、作用域的嵌套
作用域是根據(jù)名稱查找變量的一套規(guī)則睹逃。實際情況中盗扇,通常需要同時顧及幾個作用域。當(dāng)一個塊或函數(shù)嵌套在另一個塊或函數(shù)中時沉填,就發(fā)生了作用域的嵌套疗隶。因此,在當(dāng)前作用域中無法找到某個變量時翼闹,引擎就會在外層嵌套的作用域中繼續(xù)查找斑鼻,直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止猎荠。
例如以下代碼:
function foo(a) {
console.log( a + b );
}
var b = 2;
foo( 2 ); // 4
對b 進行的RHS 引用無法在函數(shù)foo 內(nèi)部完成坚弱,但可以在上一級作用域(在這個例子中就是全局作用域)中完成。
遍歷嵌套作用域鏈的規(guī)則很簡單:引擎從當(dāng)前的執(zhí)行作用域開始查找變量关摇,如果找不到荒叶,就向上一級繼續(xù)查找。當(dāng)?shù)诌_(dá)最外層的全局作用域時输虱,無論找到還是沒找到些楣,查找過程都會停止。
總結(jié):
作用域是一套規(guī)則,用于確定在何處以及如何查找變量(標(biāo)識符)愁茁。如果查找的目的是對變量進行賦值蚕钦,那么就會使用LHS 查詢;如果目的是獲取變量的值埋市,就會使用RHS 查詢冠桃。
作用域是什么 賦值操作符會導(dǎo)致LHS 查詢。=操作符或調(diào)用函數(shù)時傳入?yún)?shù)的操作都會導(dǎo)致關(guān)聯(lián)作用域 的賦值操作道宅。JavaScript 引擎首先會在代碼執(zhí)行前對其進行編譯,在這個過程中胸蛛,像var a = 2 這樣的聲 明會被分解成兩個獨立的步驟:
首先污茵,var a 在其作用域中聲明新變量。這會在最開始的階段葬项,也就是代碼執(zhí)行前進行泞当。
接下來,a = 2 會查詢(LHS 查詢)變量a 并對其進行賦值民珍。 LHS 和RHS 查詢都會在當(dāng)前執(zhí)行作用域中開始襟士,如果有需要(也就是說它們沒有找到所 需的標(biāo)識符),就會向上級作用域繼續(xù)查找目標(biāo)標(biāo)識符嚷量,這樣每次上升一級作用域(一層 樓)陋桂,最后抵達(dá)全局作用域(頂層),無論找到或沒找到都將停止蝶溶。 不成功的RHS 引用會導(dǎo)致拋出ReferenceError 異常嗜历。不成功的LHS 引用會導(dǎo)致自動隱式 地創(chuàng)建一個全局變量(非嚴(yán)格模式下),該變量使用LHS 引用的目標(biāo)作為標(biāo)識符抖所,或者拋 出ReferenceError 異常(嚴(yán)格模式下)梨州。
學(xué)習(xí)筆記,互勵共勉田轧,歡迎交流討論~
推薦書籍《javaScript高級程序設(shè)計》
歡迎訪問我的個人網(wǎng)站zhengyepan