解析一道JS面試題

更新(1/23/2018)

寫在前面,本文讓讀者產生了誤會。有這樣一些緣由:git

  1. JS爲什麼能取到地址值。
  2. a.x = a未解釋清楚。
  3. .運算符是否會對賦值運算有所幹擾。

首先:本文用addr只是一個代稱,表達的是該地址對應的那塊內存。 關於2.3點就是本次更新的緣由github

  • a.x = a賦值表達式先肯定左值(能夠這樣理解,若是不肯定我要去的地方,取到值又有什麼用呢?左邊的值在執行賦值以前就已經肯定了),而後再將右邊表達式的返回值給到左值。那麼a.x = a = {n : 2}就是從左往右先肯定a.x再肯定a,而後將返回值從右往左依次賦值給左邊。
  • .運算符會對賦值運算符有優先級干擾嗎?(會由於a.x優先級高於a = {n : 2}而先執行a.x = a嗎?)下面我來嘗試一下:
將題目改寫成:
var a = {n: 1};
var b = a;
a.x = a = a.y = {n: 2};
//改寫成這樣,那麼怎麼肯定優先級呢?
複製代碼

如今按照我文章的思路來:面試

  1. 先肯定全部左邊的地址值(再次強調指的是對應的內存!):addr(a.x) = addr(a) = addr(a.y) = addr({n : 2})
  • 咱們假設:
addr(a) = 0x100, 
addr(a.x) = 0x101, 
addr(a.y) = 0x102,
addr({n : 2}) = 0x888,
addr({n : 1}) = 0x999
複製代碼

2.(從右往左)將右邊的值賦值給左值,而後將其做爲該賦值表達式的值返回:bash

1. 先執行:addr(a.y) = addr({n : 2}),將{n : 2}的地址值存放在addr(a.y)這個地址值對應的內存!中。
(別用箭頭指,容易混淆,簡單的當作賦值就行了。本文就是犯得這個錯,致使沒有說清楚。)
2. 而後將addr(a.y) = addr({n : 2})的右值做爲該表達式的返回值N返回,在這裏會做爲下一個賦值表達式的右值。
3. 接着執行addr(a) = N,同上返回N。
4. 接着執行addr(a.x) = N,返回N。
而後咱們執行
console.log(a); // {n : 2}
console.log(a.x); // undefined
console.log(a.y); // undefined
console.log(b.x); // {n : 2}
console.log(b.y); // {n : 2}
複製代碼

不對呀,爲何給a.x和a.y的地址值對應的內存賦值了,但卻沒有東西,你是否是講錯了?

由於此時a(0x100)這塊內存存的是對{n : 2}的引用,它會去0x888這塊內存中找,這樣確定找不着x和y,由於他們在0x999內存中。ide

而b在一開始var b = a的時候,就將a(0x100)這塊內存中存的(0x999)拷貝過來。而y和x就在0x999對應的內存下。因此b找的到。函數

但願本次更新對有困惑朋友有所幫助!(也許是我講複雜了,但願多多提問。)



有這樣一道面試題lua

var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); //  undefined
alert(b.x); //  [object, Object]
複製代碼

一開始沒有太好思路,或者說是沒有想明白,通過一番折騰,算是整理清楚了思路,接下來會一一講明白,但願能對其餘人有所幫助。es5

開始以前,須要清楚賦值表達式是怎麼執行的。首先先明白什麼是什麼是右結合性和什麼是賦值表達式:spa


右結合性

賦值運算符是右結合性的,若是不知道,就請記住啦! 形如:翻譯

A = B = C = D

等價於

A = (B = (C = D))


賦值表達式

A = B這就是一個賦值表達式,而且一個賦值表達式存在一個左值和一個右值,這可不是胡編亂造的,我們說話有理有據: 引用連接(11.13.1 Simple Assignment ( = ) )

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:

  1. Let lref be the result of evaluating LeftHandSideExpression.
  2. Let rref be the result of evaluating AssignmentExpression.
  3. Let rval be GetValue(rref).
  4. Throw a SyntaxError exception if the following conditions are all true:
  • Type(lref) is Reference is true
  • IsStrictReference(lref) is true
  • Type(GetBase(lref)) is Environment Record
  • GetReferencedName(lref) is either "eval" or "arguments"
  1. Call PutValue(lref, rval).
  2. Return rval.

翻譯過來:

大致來講 賦值表達式:左邊的表達式 = 賦值表達式 具體評判的步驟以下:

  1. 將比左邊的表達式的值稱爲'lref'
  2. 將右邊賦值表達式的值稱爲'rref'
  3. 將'rval' 做爲GetValue(rref)'的返回值
  4. 若是下列狀況爲true就會報錯
  • balala~~~~
  1. 調用 PutValue(lref, rval)
  2. 返回'rval'

由上面能夠清楚的知道表達式運算的流程:

  1. 先計算左邊的表達式(很重要,先計算左邊的!這裏計算的是左邊表達式在內存中的地址值)
  2. 在計算右邊的表達式(先左後右,這裏計算的是右邊的值,這裏的值就是值,並不特指地址!如果對象則爲地址值)
  3. 計算右邊的值

(等等,這裏有疑問,爲何運算兩遍右邊?第一遍是計算右邊表達式的(+ — * /)所獲得的值,第二遍是返回這個結果值,也就是每一個表達式都有返回值!)

  1. 這裏balala~~~
  2. 調用函數PutValue,這個作的纔是給值,就是將右邊的返回值rval放進左邊的lref對應的地址值(也就是對應的內存)
  3. 而後返回這個表達式的值,也就是rval(這裏再次說明表達式有返回值,並且還能夠看出這個值是GetValue計算出的右邊的值,說白了就是等號右邊的值)

若是還沒理解的話,不要緊,往下看,我會畫圖幫助理解。


有了以上兩個知識點,下面來分析一下題目。

var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); //  undefined
alert(b.x); //  [object, Object]

複製代碼

真正難以理解的是在第三句代碼a.x = a = {n: 2} 下面我們開工吧!


  • 第1、二句代碼執行以後,內存圖以下:

  • 第三句代碼先進行改寫

a.x = a = {n: 2};
//先右結合性
a.x = (a = {n : 2})
//在計算等號左邊的值
addr(a.x) = (addr(a) = {n : 2})
//在計算等號右邊的值
addr(a.x) = ( addr(a) = value( {n : 2} ) )
複製代碼

如圖:


如上圖來進行運算 addr(a.x) = ( addr(a) = value( {n : 2} ) )

  1. 先計算全部等號左邊的值:addr(a.x)的值爲:0x888九、addr(a)的值爲:0x0001 (0x8889 <-- (0x0001 <-- (0x9999)))
  2. 在計算{n : 2}的值爲:0x9999(由於{n : 2}是一個對象,因此在這裏計算獲得的是地址值)
  3. 將0x9999做爲當前{n : 2}的返回值(也就是當前表達式的右邊的值)
  4. 將返回值賦值給0x0001(對應的內存)
  5. 返回( addr(a) = value( {n : 2} ) )的值:0x9999
  6. 將返回值賦值給0x8889(對應內存)
  7. 返回addr(a.x) = ( addr(a) = value( {n : 2} ) )的值: 0x9999

一開始找到當前表達式左右左邊的值,也就是他們地址值。而後依次將右值逐個放到對應的內存!

最後由於a的地址是指向0x9999的,且其不存x這個屬性,故返回undefined。而b的地址指向裏面存在一個保存{n:2}的地址的x屬性,故返回的是對象。

相關文章
相關標籤/搜索