iT邦幫忙

2022 iThome 鐵人賽

DAY 5
0
Modern Web

致 JavaScript 開發者的 Functional Programming 新手指南系列 第 5

Day 5:JavaScript 型別與他們的地雷(2):{ } === { } ?

  • 分享至 

  • xImage
  •  

在上個章節,我們聊到 JavaScript 其實是一種非常容易、不小心就變動到變數值的一種語言,這其實與它底層運作的記憶體位置有關,這說起來有點抽象,我們先來看看以下範例:

console.log({}==={});
console.log([]===[]);
// output 1 會是?
// output 2 會是?

針對上方兩個範例,大家覺得最後印出來的結果是什麼呢?

是的,兩個空物件去做相比,與兩個空陣列去做相比,最後的結果都會是 false !

那我們再來看看另外一個範例:

console.log(1===1);
// output 3 會是?

請問這時候印出來的結果是什麼呢?咦,竟然是 true

這時候初學 JavaScript 的朋友們就會覺得很疑惑,為什麼同樣看起來「一樣」,但在物件與基本型別上的比較,卻會出現不一樣的結果?

原因在於 JavaScript 中記憶體運作的模式,在「基本型別」與「物件」的運作模式是不一樣的!究竟是怎麼樣的機制去影響到變數的比對機制?

關於 JavaScript 記憶體的那些事

針對 JavaScript 的記憶體管理,MDN 文件用了很淺顯易懂的方式說明 JavaScript 與低階語言的差異:

「像 C 語言一樣低階的語言,都有如 malloc()free() 的函式控管記憶體權限。另一方面,當 JavaScript 建立事物(如物件、字串等)時,會分配空間給值且自動釋放不再使用的值。後者的流程稱作為記憶體的回收機制(Garbage Collection)。

這個自動化的回收流程是一個混亂的根源,它會使 JavaScript 的開發者 (或者其他高階語言的開發者) 產生可以不須理會「記憶體管理」的錯誤認知。」

如果這還不夠淺顯易懂的話,可以再換句話說:

JavaScript 並不像低階語言一般,可以靠手動的方式去操作記憶體,例如 C 語言:透過 malloc() 取得配置指定大小的記憶體空間,並回傳記憶體位置, 及 free() 手動釋放記憶體空間。

更多是透過底層的演算法去自動處理記憶體的回收機制,所以這導致初學 JavaScript 的人,可能會直接忽視記憶體這件事。

但其實 JavaScript 在記憶體有自己的管理方式,讓 JavaScript 在執行時的效率可以更高 ,這兩種方式分別為「傳值」與「傳參考」:

純值與傳值

讓我們來看看下方這個範例:

const a = 1;
const b = a;
console.log(a === b);
// true

首先我建立了一個變數 a ,接著在 b 變數中賦予與 a 變數中相同的時,此時我們將這兩個變數拿來做比對時,會獲得「true」的結果。

如果我們用具象化的方式來看這兩個變數的互動的話:

https://ithelp.ithome.com.tw/upload/images/20220907/20151147pGkF3Y5pOE.jpg

今天我們在 a 箱子中放入了一顆蘋果,然後複製一顆 a 箱子中的蘋果然後放到 b 箱子中,此時因為 b 箱子中的蘋果是我們透過複製出來的,a 箱子中的蘋果與 b 箱子中的蘋果自然是一模一樣的。

這樣透過直接複製某個變數中的值,並且賦予到另外一個變數的方式稱為「傳值」(call by value)。

物件與傳參考

還記得上方提到的這個範例嗎?

console.log({}==={});
console.log([]===[]);
// output 1 會是?
// output 2 會是?

對於沒有了解過 JavaScript 記憶體運作的人一定會覺得很神奇,為什麼兩者最終印出來的結果都是 false ?

讓我們來看另外一個範例:

const a = {
	apple: 1,
	grape: 1,
	strawberry: 1,
};

const b = a;
console.log(b === a)
// true

如果依照傳值的思維來思考物件間的值傳遞的話⋯⋯物件在變數之間的傳遞會是這樣嗎?

https://ithelp.ithome.com.tw/upload/images/20220907/20151147XLGf4mF3Mx.jpg

依照上方的邏輯,此時 a 變數中,與 b 變數中的內容物是相同的,所以印出來的結果會是 true 。

但此時狀況來了,我複製了 a 變數中的內容至 b 變數中,但此時我想要在 b 變數中的葡萄屬性的數量變為二,預期的結果會是 a 變數中的內容不變,但 b 變數中的葡萄屬性增加為二:

const a = {
	apple: 1,
	grape: 1,
	strawberry: 1,
};

const b = a;
b.grape+=1;
console.log(`a: ${a}, b: ${b}`);
// a: {apple: 1, grape: 2, strawberry: 1}, b: {apple: 1, grape: 2, strawberry: 1}
console.log(b === a);

咦?為什麼結果與預期的不同,明明想要改的值只有物件 b 的屬性,但為什麼連物件 a 的葡萄屬性也被更改了呢?

並且此時我們進行物件 b 與物件 a 的比對,會發現竟然相等,由此可知當我們嘗試「複製」某物件至某變數時,實際上這個行為並不是單純的「複製」。

假設真的是用「複製」的話,假設在程式碼中有許許多多龐大的物件,若是透過不斷地複製、裝進記憶體中,那想必電腦記憶體很快就被佔滿了!

所以 JavaScript 針對物件這種非基礎型別(也就是物件),變數之間的傳遞是使用「傳參考」的方式。

傳參考有點類似於 C 語言中的「傳址」的行為,但差異在於 JavaScript 是高階語言,我們並不能透過 JavaScript 去操作記憶體空間,程式碼並不會真的回傳一組記憶體位置給我們

實際上,範例中變數 a 與變數 b 之間的傳遞行為,更像是下圖:

https://ithelp.ithome.com.tw/upload/images/20220907/20151147uQyMnFXd8Y.jpg

當宣告變數 a 為某物件時,a 變數會指向存在某記憶體位置的某物件,當想把 a 變數的值賦予到 b 變數上時, JavaScript 做的並不是複製某物件,而是把 b 變數中的值,指向 a 變數所指向的某物件之記憶體位置中的參考物件。

所以實際上 a 變數與 b 變數共用的是同一份資料!即便官方文件稱這場變數傳遞的方式視為「傳參考」,但實際上也有開發者稱此現象為「傳共享」(call by sharing),因為變數們互相借來借去的共乘中,這些變數在 JavaScript 中,是共享同個參考物件。

話又說到此小節最一開頭的範例:

console.log({}==={});
console.log([]===[]);
// output 1 會是?
// output 2 會是?

為什麼此範例中,兩個印出結果都會 false 呢?原因在於物件進行比對的方式是依照「參考物件」來做比對,如果兩者並不是同個參考物件,自然印出來的結果都是 false。

但如果我們比照上水果箱的範例,把程式碼改一下:

const a = [];
const b = a;
console.log(a===b);
// output? 

因為變數 a 與變數 b 共用同一個參考物件,所以最後結論會相等。

小結

透過 JavaScript 傳值與傳參考的概念不難發現,JavaScript 記憶體的運作方式並不像低階語言一樣好明白,我們預期的結果不一定能跟實際的狀況相符。

同時加上是弱型別的關係, 撰寫程式碼的過程中可能會一不小心就誤改到不應該被修改的值,在 Functional Programming 中我們要極力避免上述不論是傳值或是傳參考的狀況出現。

但在聊到 FP 之前,我們還要在探討一個 JavaScript 中最重要,也會是接下來我們所要側重的重點「函式」,也是 FP 概念中最重要的主體。

參考資料:

  1. MDN - 記憶體管理

上一篇
Day 4 :JavaScript 型別與他們的地雷(1): 基本型別與物件
下一篇
Day 6 :JavaScript 型別與他們的地雷(3):函式是一等公民
系列文
致 JavaScript 開發者的 Functional Programming 新手指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
obarisk
iT邦研究生 2 級 ‧ 2022-09-12 21:43:25

其實反過來先學 c ,就不會對容器型別感到困惑了。

把型別分成原生型別跟容器型別
容器型別的變數,實際上都是存址

javascript 裡應該沒有辦法直接操作記憶體位址。

真的!自己也是看完 CS50 才知道記憶體操作這件事,也才知道 JavaScript 因為沒有 address,所以才會被稱為 call by refrence or sharing 的緣由QQ

我要留言

立即登入留言