標籤:type 類型 nts info script 有一個 變數 也有 運行時
變數的賦值操作會執行兩個動作, 首先編譯器會在當前範圍中聲明一個變數(如果之前沒有聲明過), 然後在運行時引擎會在範圍中尋找該變數, 如果能夠找到就會對它賦值。----《你所不知道的JavaScript(上)》 P7
而要講的 LHS 和 RHS 就是上面說的對變數的兩種尋找操作,尋找的過程是由範圍(詞法範圍)進行協助,但是引擎執行怎樣的尋找, 會影響最終的尋找結果。
1、LHS(Left Hand Side)和 RHS(Right Hand Side)
當變數出現在賦值操作的左側時進行 LHS 查詢, 出現在右側時進行 RHS 查詢。講得更準確一點, RHS 查詢與簡單地尋找某個變數的值別無二致, 而 LHS 查詢則是試圖找到變數的容器本身, 從而可以對其賦值。 從這個角度說, RHS 並不是真正意義上的“賦值操作的右側”, 更準確地說是“非左側”。 ----《你所不知道的JavaScript(上)》 P7
簡單來說,
(1)LHS查詢指的是找到變數的容器本身,從而可以對其進行賦值。也就是找到賦值操作的目標。LHS查詢的時候會沿著範圍鏈進行查詢,找到的話就會將值賦值給這個變數,如果到達範圍頂端仍然找不到,就會在範圍鏈頂端建立這個變數(在strict 模式中 LHS 查詢失敗時, 並不會建立並返回一個全域變數, 引擎會拋出同 RHS 查詢失敗時類似的 ReferenceError 異常) 。
var a = 2;
這裡對 a 的引用則是 LHS 引用, 因為實際上我們並不關心當前a的值是什麼, 只是想要為 =2 這個賦值操作找到一個目標。
(2)RHS查詢就是普通的查詢變數的值,即擷取變數的值。RHS查詢的時候會沿著範圍鏈進行查詢,找到的話就會取得這個值並返回,如果到達範圍頂端仍然找不到,引擎就會拋出 ReferenceError異常。如果 RHS 查詢找到了一個變數, 但是你嘗試對這個變數的值進行不合理的操作,比如試圖對一個非函數類型的值進行函數調用, 或著引用 null 或 undefined 類型的值中屬性, 那麼引擎會拋出另外一種類型的異常, 叫作 TypeError。
(註:ReferenceError 同範圍判別失敗相關, 而 TypeError 則代表範圍判別成功了, 但是對結果的操作是非法或不合理的。)
舉個栗子:
console.log(a);
這裡的a就是一個RHS引用,因為console.log需要擷取到a的值才能輸出a的值。當然這裡的console.log也是一個RHS引用,這裡對console 對象進行RHS 查詢,並且檢查得到的值中是否有一個叫作log 的方法。例子中的a因為沒有聲明過,所以會拋出錯誤。如所示:
2、執行個體詳解
執行個體1:
function foo(a) { console.log( a ); }foo( 2 );
(1)foo(..) 函數的調用需要對 foo 進行RHS引用 ,意思是“去找到 foo 的值, 並把它給我 ”。
(2)這裡還有一個容易被忽略卻非常重要的細節。代碼中隱式的 a=2 操作可能很容易被你忽略掉。這個操作發生在 2 被當作參數傳遞給foo(..) 函數時,2 會被分配給參數a。為了給參數a(隱式地)分配值,需要進行一次LHS 查詢。
(3)console.log(a)這裡還有對a進行的RHS引用,並且將得到的值傳給了console.log(..)。console.log(..) 本身也需要一個引用才能執行, 因此會對console對象進行RHS查詢,並且檢查得到的值中是否有一個叫作log的方法。
所以這裡一共進行了1次LHS查詢3次RHS查詢。
讓我們把上面這段代碼的處理過程想象成一段對話, 這段對話可能是下面這樣的。
引擎: 我說範圍, 我需要為 foo 進行 RHS 引用。 你見過它嗎?
範圍: 別說, 我還真見過, 編譯器那小子剛剛聲明了它。 它是一個函數, 給你。
引擎: 哥們太夠意思了! 好吧, 我來執行一下 foo。
引擎: 範圍, 還有個事兒。 我需要為 a 進行 LHS 引用, 這個你見過嗎?
範圍: 這個也見過, 編譯器最近把它聲名為 foo 的一個形式參數了, 拿去吧。
引擎: 大恩不言謝, 你總是這麼棒。 現在我要把 2 賦值給 a。
引擎: 哥們, 不好意思又來打擾你。 我要為 console 進行 RHS 引用, 你見過它嗎?
範圍: 咱倆誰跟誰啊, 再說我就是幹這個。 這個我也有, console 是個內建對象。給你。
引擎: 麼麼噠。 我得看看這裡面是不是有 log(..)。 太好了, 找到了, 是一個函數。
引擎: 哥們, 能幫我再找一下對 a 的 RHS 引用嗎? 雖然我記得它, 但想再確認一次。
範圍: 放心吧, 這個變數沒有變動過, 拿走, 不謝。
引擎: 真棒。 我來把 a 的值, 也就是 2, 傳遞進 log(..)。
----《你所不知道的JavaScript(上)》 P9
執行個體2:
function foo(a) { var b = a; return a + b;}var c = foo( 2 );
以上代碼中有3個LHS與4個RHS,分析如下:
(1)var c中的c需要被賦值,在賦值操作的左側,所以對c進行LHS引用。
(2)變數c需要被賦值,他的值是foo(2),那麼foo(2)的值是多少呢,需要尋找foo(2)的值,在賦值操作的右側,所以對foo(2)進行了一次RHS查詢。
(3)隱含賦值操作,將2傳遞給function foo(a){……}函數的參數a,a=2,a在賦值操作的左側,對a進行了一次LHS查詢。
(4)var b=a;中,b需要被賦值,處在賦值操作的左側,所以對b進行了一次LHS查詢,b的值將從a來,那麼右側的a的值從何而來呢?這就需要對賦值操作右側的a進行了一次RHS查詢。
(5)return a+b;中,需要找到a與b的值的來源,a與b都在賦值操作的右側,才能得到a+b的值,所以對a與b都是進行一次RHS查詢。
3、小結:
如果尋找的目的是對變數進行賦值, 那麼就會使用 LHS 查詢; 如果目的是擷取變數的值, 就會使用 RHS 查詢。賦值操作符會導致 LHS 查詢。 =操作符或調用函數時傳入參數的操作都會導致關聯範圍的賦值操作。JavaScript 引擎首先會在代碼執行前對其進行編譯, 在這個過程中, 像 var a = 2 這樣的聲明會被分解成兩個獨立的步驟:
1. 首先, var a 在其範圍中聲明新變數。 這會在最開始的階段, 也就是代碼執行前進行。
2. 接下來, a = 2 會查詢(LHS 查詢) 變數 a 並對其進行賦值。
LHS 和 RHS 查詢都會在當前執行範圍中開始, 如果有需要(也就是說它們沒有找到所需的標識符), 就會向上級範圍繼續尋找目標標識符, 這樣每次上升一級範圍, 最後抵達全域範圍, 無論找到或沒找到都將停止。不成功的 RHS 引用會導致拋出 ReferenceError 異常。 不成功的 LHS 引用會導致自動隱式地建立一個全域變數(非strict 模式下), 該變數使用 LHS 引用的目標作為標識符, 或者拋出 ReferenceError 異常(strict 模式下)。
LHS 和 RHS----你所不知道的JavaScript系列