偶的看到一段有意思的代碼:
var a = {n: 1}; a.x = a = {n: 2}; console.log(a.x);
作為一個熱衷於“鑽牛角尖”的人,樓主對這樣的代碼很感興趣,也不禁陷入了思考。so也不要說寫這樣的代碼難維護啥的,純粹為了思考邏輯。
首先,這是一個連等賦值,而且賦值的是個對象。如果是基本的JavaScript類型,不存在引用,也就不會有類似的問題,但是邏輯是一樣的。
賦值過程是怎么樣的呢?以上的翻譯是,b先賦值5,然后(b=5)會返回所賦的值(也就是5),然后再賦值給a,也就是:
a = (b = 5);
所以最初的代碼,可以修改成:
var a = {n: 1}; a.x = (a = {n: 2}); // modified here console.log(a.x);
如果是這樣,那么以下代碼有什么區別:
var a = {n: 1}; // a.x = (a = {n: 2}); // modified here a = {n: 2}; a.x = a; console.log(a.x);
很明顯,結果不一樣,繼續了解下JavaScript的運算順序。
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。
如果運算過程中有括號啥的,是不是會“智能”地改變運算順序呢?
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}
。
如果理解了,試試這道題吧:
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);
參考: