寫在前面,本文讓讀者產生了誤會。有這樣一些緣由:git
a.x = a
未解釋清楚。首先:本文用addr只是一個代稱,表達的是該地址對應的那塊內存。 關於2.3點就是本次更新的緣由github
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};
//改寫成這樣,那麼怎麼肯定優先級呢?
複製代碼
如今按照我文章的思路來:面試
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(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:
- Let lref be the result of evaluating LeftHandSideExpression.
- Let rref be the result of evaluating AssignmentExpression.
- Let rval be GetValue(rref).
- 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"
- Call PutValue(lref, rval).
- Return rval.
翻譯過來:
大致來講 賦值表達式:左邊的表達式 = 賦值表達式 具體評判的步驟以下:
- 將比左邊的表達式的值稱爲'lref'
- 將右邊賦值表達式的值稱爲'rref'
- 將'rval' 做爲GetValue(rref)'的返回值
- 若是下列狀況爲true就會報錯
- balala~~~~
- 調用 PutValue(lref, rval)
- 返回'rval'
由上面能夠清楚的知道表達式運算的流程:
(等等,這裏有疑問,爲何運算兩遍右邊?第一遍是計算右邊表達式的(+ — * /)所獲得的值,第二遍是返回這個結果值,也就是每一個表達式都有返回值!)
若是還沒理解的話,不要緊,往下看,我會畫圖幫助理解。
有了以上兩個知識點,下面來分析一下題目。
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} ) )
(0x8889 <-- (0x0001 <-- (0x9999)))
一開始找到當前表達式左右左邊的值,也就是他們地址值。而後依次將右值逐個放到對應的內存!
最後由於a的地址是指向0x9999的,且其不存x這個屬性,故返回undefined。而b的地址指向裏面存在一個保存{n:2}的地址的x屬性,故返回的是對象。