LHS 和 RHS----你所不知道的JavaScript系列(1)

變量的賦值操作會執行兩個動作, 首先編譯器會在當前作用域中聲明一個變量(如果之前沒有聲明過), 然後在運行時引擎會在作用域中查找該變量, 如果能夠找到就會對它賦值。----《你所不知道的JavaScript(上)》 P7

而要講的 LHS 和 RHS 就是上面說的對變量的兩種查找操作,查找的過程是由作用域(詞法作用域)進行協助,但是引擎執行怎樣的查找, 會影響最終的查找結果。 

1、LHS(Left Hand Side)和 RHS(Right Hand Side)

當變量出現在賦值操作的左側時進行 LHS 查詢, 出現在右側時進行 RHS 查詢。講得更準確一點, RHS 查詢與簡單地查找某個變量的值別無二致, 而 LHS 查詢則是試圖找到變量的容器本身, 從而可以對其賦值。 從這個角度說, RHS 並不是真正意義上的“賦值操作的右側”, 更準確地說是“非左側”。  ----《你所不知道的JavaScript(上)》 P7

  簡單來說,

  (1)LHS查詢指的是找到變量的容器本身,從而可以對其進行賦值。也就是找到賦值操作的目標。LHS查詢的時候會沿着作用域鏈進行查詢,找到的話就會將值賦值給這個變量,如果到達作用域頂端仍然找不到,就會在作用域鏈頂端創建這個變量(嚴格模式中 LHS 查詢失敗時, 並不會創建並返回一個全局變量, 引擎會拋出同 RHS 查詢失敗時類似的 ReferenceError 異常) 。

var a = 2;

這裏對 的引用則是 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

  引擎: 作用域, 還有個事兒。 我需要爲 進行 LHS 引用, 這個你見過嗎?

  作用域: 這個也見過, 編譯器最近把它聲名爲 foo 的一個形式參數了, 拿去吧。

  引擎: 大恩不言謝, 你總是這麼棒。 現在我要把 賦值給 a

  引擎: 哥們, 不好意思又來打擾你。 我要爲 console 進行 RHS 引用, 你見過它嗎?

  作用域: 咱倆誰跟誰啊, 再說我就是幹這個。 這個我也有, console 是個內置對象。給你。

  引擎: 麼麼噠。 我得看看這裏面是不是有 log(..)。 太好了, 找到了, 是一個函數。

  引擎: 哥們, 能幫我再找一下對 的 RHS 引用嗎? 雖然我記得它, 但想再確認一次。

  作用域: 放心吧, 這個變量沒有變動過, 拿走, 不謝。

  引擎: 真棒。 我來把 的值, 也就是 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查詢。

注:console.log(..) 本身也需要一個引用才能執行,因此會對console 對象進行RHS 查詢,並且檢查得到的值中是否有一個叫作log 的方法。這裏不會再對log進行RHS查詢。因爲對console查詢完畢後,對象屬性訪問規則會接管對log屬性的訪問。也就是說,如果是訪問對象的屬性就不存在LHS查詢和RHS查詢了,找不到就返回undefined。

 

 

3、小結:

如果查找的目的是對變量進行賦值, 那麼就會使用 LHS 查詢; 如果目的是獲取變量的值, 就會使用 RHS 查詢。賦值操作符會導致 LHS 查詢。 =操作符或調用函數時傳入參數的操作都會導致關聯作用域的賦值操作。JavaScript 引擎首先會在代碼執行前對其進行編譯, 在這個過程中, 像 var a = 2 這樣的聲明會被分解成兩個獨立的步驟:

1. 首先, var a 在其作用域中聲明新變量。 這會在最開始的階段, 也就是代碼執行前進行。

2. 接下來, a = 2 會查詢(LHS 查詢) 變量 a 並對其進行賦值。

LHS 和 RHS 查詢都會在當前執行作用域中開始, 如果有需要(也就是說它們沒有找到所需的標識符), 就會向上級作用域繼續查找目標標識符, 這樣每次上升一級作用域, 最後抵達全局作用域, 無論找到或沒找到都將停止。不成功的 RHS 引用會導致拋出 ReferenceError 異常。 不成功的 LHS 引用會導致自動隱式地創建一個全局變量(非嚴格模式下), 該變量使用 LHS 引用的目標作爲標識符, 或者拋出 ReferenceError 異常(嚴格模式下)。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章