天天看點

千萬不要在JS中使用連等指派操作

前言

文章标題這句話原本是在國外某JavaScript規範裡看到的,當時并沒有引起足夠的重視,直到最近一次出現了bug發現JS裡的連等指派操作的特色(坑)。

網上搜尋一番發現一個非常好的連等指派的(來源1,來源2)例子:

var a = {n:1};      
a.x = a = {n:2};      
console.log(a.x); // 輸出?      

答案是:

千萬不要在JS中使用連等指派操作
千萬不要在JS中使用連等指派操作
console.log(a.x); // undefined      

答案

不知道各位有沒有答對,至少我是答錯了。

遂借此機會好好看看JS連等指派是怎麼回事

指派順序?

假設有一句代碼: A=B=C; ,指派語句的執行順序是從右至左,是以問題在于:

是猜想1: B = C; A = C; ?

還是猜想2: B = C; A = B;  ?

我們都知道若兩個對象同時指向一個對象,那麼對這個對象的修改是同步的,如:

var a={n:1};
var b=a;
a.n=2;
console.log(b);//Object {n: 2}      

是以可以根據這個特性來測試連續指派的順序。

按照猜想1,把C換成具體的對象,可以看到對a的修改不會同步到b上,因為在執行第一行和第二行時分别建立了兩個 {n:1} 對象。如:

var b={n:1};
var a={n:1};
a.n=0;
console.log(b);//Object {n: 1}      

再按照猜想2,把C換成具體的對象,可以看到對a的修改同步到了b,因為a和b同時引用了一個對象,如:

var b={n:1};
var a=b;
a.n=0;
console.log(b);//Object {n: 0}      

測試真正的連等指派:

var a,b;
a=b={n:1};
a.n=0;
console.log(b);//Object {n: 0}      

可以看到是符合猜想2的,如果有人覺得這個測試不準确可以再來測試,使用ECMA5的setter和getter特性來測試。

首先setter和getter是應用于變量名的,而不是變量真正儲存的對象,如下:

Object.defineProperty(window,"obj",{
  get:function(){
    console.log("getter!!!");
  }
});
var x=obj;
obj;//getter!!! undefined
x;//undefined      

可以看到隻有obj輸出了“getter!!!”,而x沒有輸出,用此特性來測試。

連等指派測試2:

Object.defineProperty(window,"obj",{
  get:function(){
    console.log("getter!!!");
  }
});
a=b=obj;//getter!!!  undefined      
千萬不要在JS中使用連等指派操作

通過getter再次證明,在A=B=C中,C隻被讀取了一次。

是以,連等指派真正的運算規則是  B = C; A = B;  即連續指派是從右至左永遠隻取等号右邊的表達式結果指派到等号左側。

連續指派能拆開寫麼?

通過上面可以看到連續指派的真正規則,那麼再回歸到文章開頭的那個案例,如果按照上述規則将連續指派拆開會發現結果不一樣了,如:

var a={n:1};
a={n:2};
a.x=a;
console.log(a.x);//Object {n: 2, x: Object}      

是以連續指派語句雖然是遵從從右至左依次指派的規則但依然不能将語句拆開來寫,至于為什麼

我猜測:js内部為了保證指派語句的正确,會在一條指派語句執行前,先把所有要指派的引用位址取出一個副本,再依次指派。

是以我認為這段代碼  a.x=a={n:2};  的邏輯是:

1、在執行前,會先将a和a.x中的a的引用位址都取出來,此值他們都指向{n:1}

2、在記憶體中建立一個新對象{n:2}

3、執行a={n:2},将a的引用從指向{n:1}改為指向新的{n:2}

4、執行a.x=a,此時a已經指向了新對象,而a.x因為在執行前保留了原引用,是以a.x的a依然指向原先的{n:1}對象,是以給原對象新增一個屬性x,内容為{n:2}也就是現在a

5、語句執行結束,原對象由{n:1}變成{n:1,x:{n:2}},而原對象因為無人再引用他,是以被GC回收,目前a指向新對象{n:2}

6、是以就有了文章開頭的運作結果,再執行a.x,自然就是undefined了

上述過程按序号圖示:

千萬不要在JS中使用連等指派操作
千萬不要在JS中使用連等指派操作

按照上述過程可以看出舊的a.x和新的a都指向新建立的對象{n:2},是以他們應該是全等的。

測試:

var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a===b.x); //true      
千萬不要在JS中使用連等指派操作

因為我們增加了var b=a,即将原對象增加了一條引用,是以在上述第5步時不會被釋放,證明了上面的結論。

後記

通過這次了解了連續指派的特點,再回過頭看文章标題,似乎應該叫:

盡量不要使用JS的連續指派操作,除非真的了解它的内部機制及可能會産生的後果。

(完)

原文連結-http://www.cnblogs.com/xxcanghai/p/4998076.html

如果您認為本文對得起您所閱讀他所花的時間,歡迎點選右下角↘ 推薦。您的支援是我繼續寫作最大的動力,謝謝

作者:小小滄海

出處:http://www.cnblogs.com/xxcanghai/

本文位址:http://www.cnblogs.com/xxcanghai/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。