- 你不知道的JavaScript(上卷)
- (美)凱爾辛普森
- 2591字
- 2019-06-06 10:48:11
1.2 理解作用域
我們學(xué)習(xí)作用域的方式是將這個過程模擬成幾個人物之間的對話。那么,由誰進行這場對話呢?
1.2.1 演員表
首先介紹將要參與到對程序var a = 2;進行處理的過程中的演員們,這樣才能理解接下來將要聽到的對話。
· 引擎
從頭到尾負(fù)責(zé)整個JavaScript程序的編譯及執(zhí)行過程。
· 編譯器
引擎的好朋友之一,負(fù)責(zé)語法分析及代碼生成等臟活累活(詳見前一節(jié)的內(nèi)容)。
· 作用域
引擎的另一位好朋友,負(fù)責(zé)收集并維護由所有聲明的標(biāo)識符(變量)組成的一系列查詢,并實施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對這些標(biāo)識符的訪問權(quán)限。
為了能夠完全理解JavaScript的工作原理,你需要開始像引擎(和它的朋友們)一樣思考,從它們的角度提出問題,并從它們的角度回答這些問題。
1.2.2 對話
當(dāng)你看見var a = 2;這段程序時,很可能認(rèn)為這是一句聲明。但我們的新朋友引擎卻不這么看。事實上,引擎認(rèn)為這里有兩個完全不同的聲明,一個由編譯器在編譯時處理,另一個則由引擎在運行時處理。
下面我們將var a = 2;分解,看看引擎和它的朋友們是如何協(xié)同工作的。
編譯器首先會將這段程序分解成詞法單元,然后將詞法單元解析成一個樹結(jié)構(gòu)。但是當(dāng)編譯器開始進行代碼生成時,它對這段程序的處理方式會和預(yù)期的有所不同。
可以合理地假設(shè)編譯器所產(chǎn)生的代碼能夠用下面的偽代碼進行概括:“為一個變量分配內(nèi)存,將其命名為a,然后將值2保存進這個變量。”然而,這并不完全正確。
事實上編譯器會進行如下處理。
1.遇到var a,編譯器會詢問作用域是否已經(jīng)有一個該名稱的變量存在于同一個作用域的集合中。如果是,編譯器會忽略該聲明,繼續(xù)進行編譯;否則它會要求作用域在當(dāng)前作用域的集合中聲明一個新的變量,并命名為a。
2.接下來編譯器會為引擎生成運行時所需的代碼,這些代碼被用來處理a = 2這個賦值操作。引擎運行時會首先詢問作用域,在當(dāng)前的作用域集合中是否存在一個叫作a的變量。如果是,引擎就會使用這個變量;如果否,引擎會繼續(xù)查找該變量(查看1.3節(jié))。
如果引擎最終找到了a變量,就會將2賦值給它。否則引擎就會舉手示意并拋出一個異常!
總結(jié):變量的賦值操作會執(zhí)行兩個動作,首先編譯器會在當(dāng)前作用域中聲明一個變量(如果之前沒有聲明過),然后在運行時引擎會在作用域中查找該變量,如果能夠找到就會對它賦值。
1.2.3 編譯器有話說
為了進一步理解,我們需要多介紹一點編譯器的術(shù)語。
編譯器在編譯過程的第二步中生成了代碼,引擎執(zhí)行它時,會通過查找變量a來判斷它是否已聲明過。查找的過程由作用域進行協(xié)助,但是引擎執(zhí)行怎樣的查找,會影響最終的查找結(jié)果。
在我們的例子中,引擎會為變量a進行LHS查詢。另外一個查找的類型叫作RHS。
我打賭你一定能猜到“L”和“R”的含義,它們分別代表左側(cè)和右側(cè)。
什么東西的左側(cè)和右側(cè)?是一個賦值操作的左側(cè)和右側(cè)。
換句話說,當(dāng)變量出現(xiàn)在賦值操作的左側(cè)時進行LHS查詢,出現(xiàn)在右側(cè)時進行RHS查詢。
講得更準(zhǔn)確一點,RHS查詢與簡單地查找某個變量的值別無二致,而LHS查詢則是試圖找到變量的容器本身,從而可以對其賦值。從這個角度說,RHS并不是真正意義上的“賦值操作的右側(cè)”,更準(zhǔn)確地說是“非左側(cè)”。
你可以將RHS理解成retrieve his source value(取到它的源值),這意味著“得到某某的值”。
讓我們繼續(xù)深入研究。
考慮以下代碼:
console.log(a);
其中對a的引用是一個RHS引用,因為這里a并沒有賦予任何值。相應(yīng)地,需要查找并取得a的值,這樣才能將值傳遞給console.log(..)。
相比之下,例如:
a = 2;
這里對a的引用則是LHS引用,因為實際上我們并不關(guān)心當(dāng)前的值是什么,只是想要為=2這個賦值操作找到一個目標(biāo)。
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查詢和賦值的形式并不合適。
1.2.4 引擎和作用域的對話
function foo(a) {
console.log(a); // 2
}
foo(2);
讓我們把上面這段代碼的處理過程想象成一段對話,這段對話可能是下面這樣的。
引擎:我說作用域,我需要為foo進行RHS引用。你見過它嗎?
作用域:別說,我還真見過,編譯器那小子剛剛聲明了它。它是一個函數(shù),給你。
引擎:哥們太夠意思了!好吧,我來執(zhí)行一下foo。
引擎:作用域,還有個事兒。我需要為a進行LHS引用,這個你見過嗎?
作用域:這個也見過,編譯器最近把它聲名為foo的一個形式參數(shù)了,拿去吧。
引擎:大恩不言謝,你總是這么棒。現(xiàn)在我要把2賦值給a。
引擎:哥們,不好意思又來打擾你。我要為console進行RHS引用,你見過它嗎?
作用域:咱倆誰跟誰啊,再說我就是干這個。這個我也有,console是個內(nèi)置對象。給你。
引擎:么么噠。我得看看這里面是不是有l(wèi)og(..)。太好了,找到了,是一個函數(shù)。
引擎:哥們,能幫我再找一下對a的RHS引用嗎?雖然我記得它,但想再確認(rèn)一次。
作用域:放心吧,這個變量沒有變動過,拿走,不謝。
引擎:真棒。我來把a的值,也就是2,傳遞進log(..)。
……
1.2.5 小測驗
檢驗一下到目前的理解程度。把自己當(dāng)作引擎,并同作用域進行一次“對話”:
function foo(a) { var b = a; return a + b; } var c = foo(2);
1.找到其中所有的LHS查詢。(這里有3處!)
2.找到其中所有的RHS查詢。(這里有4處!)
查看本章小結(jié)中的參考答案。
- 技術(shù)領(lǐng)導(dǎo)力:程序員如何才能帶團隊
- Instant Typeahead.js
- jQuery從入門到精通 (軟件開發(fā)視頻大講堂)
- 64位匯編語言的編程藝術(shù)
- Linux網(wǎng)絡(luò)程序設(shè)計:基于龍芯平臺
- Visual C
- Xamarin.Forms Projects
- Python圖形化編程(微課版)
- Mastering SciPy
- 實戰(zhàn)Python網(wǎng)絡(luò)爬蟲
- PHP項目開發(fā)全程實錄(第4版)
- 虛擬現(xiàn)實建模與編程(SketchUp+OSG開發(fā)技術(shù))
- 從零開始學(xué)Unity游戲開發(fā):場景+角色+腳本+交互+體驗+效果+發(fā)布
- 美麗洞察力:從化妝品行業(yè)看顧客需求洞察
- TensorFlow.NET實戰(zhàn)