by reference (傳參考)、by value(傳值)的差別
在參加鐵人賽的時候,因為這個題目了解的朦朦朧朧、似懂非懂,所以不敢寫這個題目。直到JS直播班聽了老師的解說,才一秒突破盲腸,恍然大悟。
談這個題目之前,先來做一下六角學院JS直播班第一週的周末作業《記憶體接龍》,以了解變數與記憶體儲存位置的關係,這樣對於by reference (傳參考)、by value(傳值)會有更深入的了解。
第 1 題
記憶體空間畫圖來表示:
備註: let a ;的值為 “ undefined “ ,也會佔記憶體空間。
所以答案2應該是: 1 個變數, 3 個型別, 3 個記憶體空間
助教的回答:let a因為並沒有宣告a的值,所以a會有一個undefined 的值,並且佔了一個記憶體空間,所以這題答案應該是1個變數, 3 個型別(數字、字串、 undefined ), 3 個記憶體空間。
第 2 題
記憶體空間畫圖來表示:
第 3 題
記憶體空間畫圖來表示:
我們由上面的作業可以發現,不同的變數指向不同的記憶體位置,只要變數重新賦值之後,就會把新的值存到新的記憶體空間之中,舊的值就從記憶體上面清空,而運算過程中的值也會佔到記憶體的空間。
這就是JS變數與記憶體之間運作的過程。
還有一個觀念要先記一下, JS 的變數本身沒有型別,它被賦予的值才有。
理解了這些才能進一步來談 by reference (傳參考)、 by value (傳值)的差別。
by value(傳值)
讓我們繼續來《射鵰英雄歪傳》,郭靖跟黃蓉小倆口結婚後,在大漠開起了寵物店,專門賣汗血寶馬和神雕,因為都是珍稀之物,所以定價都是1000兩黃金。
我們可以觀察到,在基本型別的時候,不同的變數指向不同的記憶體位置,兩個變數賦予的值一樣,也就是記憶體儲存的值一樣,兩個變數就相等。所以我們可以歸納出,基本型別變數的比較,我們看的是它被賦予的值,值相等,兩個變數就相等。
繼續來《射鵰英雄歪傳》,有一天楊康來寵物店想買一隻汗血馬,問郭靖多少錢?因為楊康之前買過神雕,郭靖隨口就說:「汗血馬跟神雕一樣的價格!」
楊康心想:「老子最近沒錢!」就說:「兄弟!這馬也太貴了!」郭靖說:「蓉妹說不二價,兄弟!聽某嘴大富貴!」於是楊康只好忍痛去跟大漠的高利貸歐陽克借錢買了一匹汗血馬。
結果過幾天,成吉斯汗打敗大宛國,擄獲許多汗血寶馬,造成大漠上汗血寶馬的價格大跌價,一匹馬變成300兩黃金。
我們就用 JavaScript 來說說這件事:
楊康哭哭!汗血馬的價格不是跟神雕一樣嗎?
讓我們用前面畫圖的練習來理解一下JavaScript發生了甚麼事!
- 宣告一個eaglePrice變數,給它1000的值
- 宣告一個horsePrice變數,給它的值是變數eaglePrice。
這時發生的事情就是,horsePrice 去拷貝了 eaglePrice 的值 1000 到自己目前占用的記憶體空間。前面有提到「基本型別變數的比較,我們看的是它被賦予的值,值相等,兩個變數就相等」。
let horsePrice = eaglePrice;
這時候eaglePrice === horsePrice的布林值為true。
horsePrice重新賦值為300這個行為指的是,horsePrice去佔用了新的記憶體空間儲存了新的值300,這時horsePrice === eaglePrice的布林值就是false。
汗血馬的價格horsePrice與神雕的價格eaglePrice是各自獨立的,當值相等時,兩個變數才相等,汗血馬價格崩盤的時候,神雕的價格依然不受影響。
所以我們可以說「基本型別」變數之間的比較,看的是它被賦予的值相不相等,這種現象被稱為「by value(傳值)」。
by reference (傳參考)
但是在「物件型別」的比較上,是另外一種情形。
繼續來《射鵰英雄歪傳》,郭靖的寵物店因為生意太好,所以開了分店,分店裡面汗血馬的價格跟總店是一樣的。
我們發現當分店汗血馬的價格branchStore.horsePrice被重新賦值為300時,總店的汗血馬的價格也跟著變為300。
而console.log(mainStore === branchStore)的結果為true,我們可已發現mainStore與branchStore指向的是同一個實體。
我們來看看宣告物件型別變數時,記憶體是如何運作的。
當我們let mainStore = {horsePrice: 1000};其實是把mainStore的參考位置指向記憶體中存放物件的位置。
所以當我們let branchStore = mainStore;也是把branchStore變數參考的位置指向mainStore所參考的變數位置,所以當物件horsePrice屬性的值改變的時候,mainStore跟branchStore的值都會跟著變動。
物件型別是透過「引用」的方式在傳遞資料,物件型別的物件的屬性值其實引用的是記憶體儲存資料的參考, 所以我們會說在物件型別的比較是by reference(傳參考),看這兩個物件是否指向相同的記憶體空間,參考相同的值。
凡事都有例外,物件的例外讓人特別困惑。
繼續來《射鵰英雄歪傳》,郭靖寵物店生意很好,所以黃蓉也開了一家分店,有一天夫妻倆吵架,黃蓉一氣之下脫離加盟體系,開始削價競爭。
在這種情形之下,husbandStore與wifeStore原本是引用相同的參考位置,但是wifeStore重新賦值之後,則引用新的參考位置,所以汗血馬價格變動的時候,husbandStore與wifeStore兩者不會連動,因為兩者參考的是不同的物件實體。
許國政先生認為這種物件型別的比較應該更像是「by sharing」,這有點玄!
且讓我們引用他在《0 陷阱!0 誤解!8 天重新認識JavaScript!》的一段話作為總結:
「由於JavaScript的物件類型是可變的(mutable),當物件更新時,會影響到所有引用這個物件的變數與副本,修改時會變動到原本的參考。但當賦與新值時,卻會產生新的實體參考。」
參考資料