為什麼資料型別會影響資料存取?淺談 Primitive Type 與 Reference Type 的差異

學.誌|Chris Kang
5 min readFeb 20, 2023
Photo by Pankaj Patel on Unsplash

當我們在進行變數(identifier)賦值或變數複製時,可能會遇到一個問題––––當我複製一般的原始型(Primitive)變數時,都能順利複製與修改兩個不同參數;但面對如 Object 等引用型(Reference Type)變數時,就會無法照預期修改。

這也引出了一個 JavaScript 時常被問到的問題–––淺層複製與深層複製,但這個就超出本偏文章的討論範圍。在這一篇文章中,我想和讀者一起探討變數類型之間的區別,以及它們在 JavaScript 中的使用情況。

想進一步了解背後原因的讀者,可以搜尋 Call-Stack 和 Heap 在記憶體管理的原理,可以更瞭解為何有這個資料型態的差異。

什麼是原始型(Primitive Type)變數和引用型(Reference Type)變數?

引用型(Reference Type)變數與原始型(Primitive Type)變數之間的主要區別,在於引用型變數存儲的是對象的地址,而不是實際的數據。

這意味著當我們對一個引用型變數進行操作時,我們實際上是在操作該對象,而不是在操作變數本身。這與原始型變數並不一樣,因為在原始型變數上,我們直接操作的是存儲在變數中的數據本身。

但在實際探討具體差異前,先讓我們瞭解各自的定義。

原始型變數

以下的資料型態,皆屬於原始型(Primitive Type)變數。

引用型變數

引用型變數是指那些函式、Array 甚至物件等複雜數據的變數。

  • function
  • Array
  • object

原始型變數和引用型變數如何被創建?

再說明之前,先簡單提到 Call Stack 和 Heap 這兩個 JavaScript 的記憶體分配。這裡可以先簡化理解為:

  • Call Stack:作為直接執行的記憶體儲存位置
  • Heap:作為稍後存取(Callback)的記憶體儲存位置

原始型(Primitive Type)變數的儲存過程

我們創建一個原始型變數時,JavaScript 會在被稱為 Call Stack 的記憶體中,為該變數分配一個空間,並將值存儲在這個空間中。

舉例來說,當我們儲存一個 x = 5 的變數時,會在記憶體 Call Stack 區分配一個給 x 的儲存空間,接著將 5 儲存在這個空間中。接著 x 則會儲存 5 這個數值存在的記憶體位置(如記憶體 001 位置,並在 001–0xx 間儲存 5 這個值)。

如果我們稍後給 x = 7,那 7 這個數值就會儲存到一個新的記憶體位置,並將該記憶體位置賦值給 x。此時原先的 5 就不會有任何 identifier 會指向,因此該記憶體就會回收。原始型變數在 JavaScript 中非常簡單和直觀。

引用型(Reference Type)變數的儲存過程

與原始型變數不同的是,當我們創建一個引用型變數時,JavaScript 會在被稱為 Call Stack 的記憶體中,存儲對象或數組的在 Heap 中的參考地址。

舉例來說,當我執行一個 obj = {name: Chris} 的 Object 時,JavaScript 會將 Object 的實際內容儲存在 Heap 中,並將 Heap 的參考地址儲存在 Call Stack 中。

如果我們稍後再次給 obj 賦值,JavaScript 不會創建一個新的記憶體空間,而是直接將新的參考位置存儲在變數 obj 中。

而之所以會有 Call Stack 和 Heap 兩者儲存不同的變數型態,是因為在 Call Stack 中僅能儲存較小而簡單的資料型態;而 Heap 在某種意義上則能儲存不限容量的資料型態。

引用型變數在操作時可能產生的問題

因為我們在給引用型變數賦值時,實際上我們給的是記憶體位置。因此如果我們在後續操作該對象,則實際上我們是再操作該對象本身。

也因如此,就算對引用型變數採用 const 來避免重新賦值,一樣可以對該對象進行修改。因為本身該對象的記憶體位置,在 Call Stack 中並沒有被修改,修改的資料實際上是在 Heap 當中。

另一個需要注意的地方,是記憶體回收的問題。因此如果我們不再需要一個對象(如 Object),就需要確認沒有任何引用型變數指向該對象,否則該對象就無法被回收。

小總結

簡單來說,原始型變數和引用型變數在 JavaScript 中各有優缺點;但實際使用時,需要小心進行各個變數類型的複製,避免導致副作用而污染到變數。

如果真的要進行深層複製,僅有一層的 Object 可以使用 const newObj = Object.assign({}, obj) 來進行複製,但若 Object 超過一層,就需要使用如 Lo-Dash 這樣的套件進行深層複製。

--

--

學.誌|Chris Kang

嗨!我是 Chris,一位擁有技術背景的獵頭,熱愛解決生活與職涯上的挑戰。專注於產品管理/資料科學/前端開發 / 人生成長,在這條路上,歡迎你找我一起聊聊。歡迎來信合作和交流: chriskang0917@gmail.com