偶的看到一段有意思的程式碼:
1 2 3 |
var a = {n: 1}; a.x = a = {n: 2}; console.log(a.x); |
作為一個熱衷於“鑽牛角尖”的人,樓主對這樣的程式碼很感興趣,也不禁陷入了思考。so也不要說寫這樣的程式碼難維護啥的,純粹為了思考邏輯。
首先,這是一個連等賦值,而且賦值的是個物件。如果是基本的JavaScript型別,不存在引用,也就不會有類似的問題,但是邏輯是一樣的。
賦值過程是怎麼樣的呢?以上的翻譯是,b先賦值5,然後(b=5)會返回所賦的值(也就是5),然後再賦值給a,也就是:
1 |
a = (b = 5); |
所以最初的程式碼,可以修改成:
1 2 3 |
var a = {n: 1}; a.x = (a = {n: 2}); // modified here console.log(a.x); |
如果是這樣,那麼以下程式碼有什麼區別:
1 2 3 4 5 |
var a = {n: 1}; // a.x = (a = {n: 2}); // modified here a = {n: 2}; a.x = a; console.log(a.x); |
很明顯,結果不一樣,繼續瞭解下JavaScript的運算順序。
1 2 3 4 5 6 7 8 |
var a = 10; function fn() { a = 20; return 20; } var b = a + fn(); console.log(b); |
輸出30,我們猜測JavaScript是從左到右運算的(事實也確實如此)。先取出a在記憶體中的值(10),然後執行fn函式,返回20(同時改變了a的值),10+20=30。
如果運算過程中有括號啥的,是不是會“智慧”地改變運算順序呢?
1 2 3 4 5 6 7 8 |
var a = 10; function fn() { a = 20; return 20; } var b = a + (fn() * 2); console.log(b); |
輸出50,還是從左到右,沒有智慧地先運算括號裡的內容,然後改變a的值,再和a相加。這在JavaScript核心中是怎麼實現的?這個我也沒有研究過,但是我知道C++等語言是沒有這麼“智慧”的,可以通過堆疊來模擬,比如簡單計算器。
再回頭看這道題,宣告一個變數a,指向一個物件{n: 1},然後執行連等。可以肯定的是,a.x和a指向的是同一塊記憶體,我們從左到右運算,a.x實際操作的是a指向的物件,發現沒有x這個key,那麼就會自動新增,它的值等待右邊的運算結果。然後a重新引用了一個物件,並且之前的{n:1}這個物件新的key(x)的value也引用到同一個物件上。大概過程如下圖,過程2我把它理解為等待,等待過程3中a的運算結果。
如果要說的稍微“專業”一點,連等是先確定所有變數的指標,再讓指標指向那個賦值。
事實上,解析器在接受到?a.x = a = {n:2}
?這樣的語句後,會這樣做:
- 找到 a 和 a.x 的指標。如果已有指標,那麼不改變它。如果沒有指標,即那個變數還沒被申明,那麼就建立它,指向 null。
a 是有指標的,指向?{n:1}
;a.x 是沒有指標的,所以建立它,指向 null。 - 然後把上面找到的指標,都指向最右側賦的那個值,即?
{n:2}
。
如果理解了,試試這道題吧:
1 2 3 4 |
var a, b, c, d; a = b = c = d = {a: 1}; a.x = a = b.y = b = c.z = c = {} console.log(a, b, c, d); |
參考: