JavaScript 資料是怎麼儲存的?深入了解 Value Type 與 Reference Type

更新日期: 2025 年 4 月 19 日

你可能遇過這種狀況:

const a = [1, 2, 3];
const b = [1, 2, 3];
console.log(a === b); // false

看起來陣列內容一模一樣,為什麼卻不相等?

又或者:

const user = { name: "Amy" };
const users = [user];
user.name = "Bob";
console.log(users[0].name); // "Bob"

你沒有動到 users 陣列,但裡面的值卻被改變了?

這一切的背後,跟 JavaScript 如何儲存「原始資料」和「物件資料」有關,也就是:值類型(Value Type)與參考類型(Reference Type)


資料類型大分類

JavaScript 將資料分成兩大類,每一類的儲存與處理方式都不一樣:

值類型(Value Types)— 存放「值」本身

包含:

  • string
  • number
  • boolean
  • null
  • undefined
  • symbol
  • bigint

這些值是「簡單且不可變」的,變數儲存的就是資料本體

const a = 100;
const b = 100;
console.log(a === b); // true,因為內容一樣

參考類型(Reference Types)— 存放「指向記憶體位置的參考」

包含:

  • object
  • array
  • function

這些資料結構比較複雜,變數儲存的是「記憶體位置(Reference)」,也就是:資料本體其實存在別的地方。

const obj1 = { id: 1 };
const obj2 = { id: 1 };
console.log(obj1 === obj2); // false,即使內容看起來一樣

儲存邏輯差在哪?記憶體觀念圖解

類型儲存在變數中的內容比較時行為是否共享資料
值類型值的本體比較的是值❌ 各自獨立
參考類型記憶體參考(指標)比較的是記憶體位置✅ 指向同一資料

📌 這就是為什麼:

[1, 2] === [1, 2]; // false,因為不同記憶體位置
flowchart LR
    subgraph 程式碼
    A["let age = 30;"]
    B["let age1 = age;"]
    C["age = 31;"]
    D["let obj = { name: 'Tom' };"]
    E["let obj2 = obj;"]
    F["obj2.name = 'Jerry';"]
    end
    
    subgraph 記憶體儲存格
    subgraph 值類型儲存區 Stack
    M1[("00XX1<br><i>記憶體位置編號</i>")] --- V1["30<br><i>儲存值</i>"]
    M2[("00XX2<br><i>記憶體位置編號</i>")] --- V2["31<br><i>儲存值</i>"]
    M3[("00XX3<br><i>記憶體位置編號</i>")] --- V3["30<br><i>儲存值</i>"]
    end
    
    subgraph 參考類型儲存區 Heap
    M4[("00YY1<br><i>變數記憶體位置</i>")] --- V4["FF01<br><i>儲存參考位址</i>"]
    M5[("00YY2<br><i>變數記憶體位置</i>")] --- V5["FF01<br><i>儲存參考位址</i>"]
    
    M6[("FF01<br><i>物件實際記憶體位置</i>")] --- V6["{name: 'Jerry'}<br><i>物件實際內容</i>"]
    end
    end
    
    A -->|"建立變數"| M1
    B -->|"複製值"| M3
    C -->|"修改值"| M2
    D -->|"建立物件參考"| M4
    E -->|"複製參考"| M5
    
    V4 -->|"指向實際物件"| M6
    V5 -->|"指向同一個物件"| M6
    
    
    style M1 fill:#ddf8dd,stroke:#000000
    style M2 fill:#ddf8dd,stroke:#000000
    style M3 fill:#ddf8dd,stroke:#000000
    style M4 fill:#fff0f0,stroke:#000000
    style M5 fill:#fff0f0,stroke:#000000
    style M6 fill:#fff0f0,stroke:#000000
    
    style V1 fill:#ddf8dd,stroke:#000000
    style V2 fill:#ddf8dd,stroke:#000000
    style V3 fill:#ddf8dd,stroke:#000000
    style V4 fill:#fff0f0,stroke:#000000,color:#008800
    style V5 fill:#fff0f0,stroke:#000000,color:#008800
    style V6 fill:#fff0f0,stroke:#000000



常見應用情境:比較、修改、複製資料的正確觀念

這一章節,我們會從實務常見的幾種操作出發,說明「值類型」和「參考類型」在 JavaScript 中的實際行為差異。

比較值 vs. 比較參考:看的是值,還是地址?

🧾 值類型的比較 —— 內容一致就相等

console.log(5 === 5); // true
console.log("hello" === "hello"); // true
  • 值類型(例如 numberstring)的比較是「看資料本身是否相等」。
  • 兩邊的內容一樣,就會被視為相等。

🧠 想像成比對兩張紙條,兩張紙都寫著 “5”,你當然會說它們一樣。

🧾 參考類型的比較 —— 看的是記憶體位置

console.log([1, 2, 3] === [1, 2, 3]); // false
console.log({ name: "Tom" } === { name: "Tom" }); // false
  • 即使資料內容「一模一樣」,JavaScript 也會判斷為 不同
  • 因為它們是兩個「不同記憶體位置」的資料。

🧠 就像兩份一模一樣的簡報檔案,但儲存在不同的 USB 隨身碟裡——你拿來比較時,是比「是哪個隨身碟」,不是「簡報長什麼樣」。

修改值 vs. 修改參考:哪裡在變?

🧾 修改值類型:變數之間彼此獨立

let scoreA = 80;
let scoreB = 80;

scoreA = 100;

console.log(scoreA); // 100
console.log(scoreB); // 80 ✅ 不受影響

🧠 比喻:就像你給了兩個人各自的紙本成績單

A 把自己的擦掉重寫成 100,B 的那份完全沒改變。

🧾 修改參考類型:共用資料,同步變動

const student = { name: "Mary" };
const studentA = student;
const studentB = student;

studentA.name = "John";

console.log(studentB.name); // "John" ❗️也變了

🧠 比喻:你把同一個 Google 文件連結給 A 和 B

A 把名字改了,B 一打開,也看到已經改成「John」的版本。


如何正確「複製」參考類型?

當你不希望物件之間互相影響(例如修改 A 不要影響 B),就必須「複製資料內容」而不是「複製參考」。

展開運算子(Shallow Copy)—— 適用於物件扁平結構

const original = { name: "David" };
const copy = { ...original };

copy.name = "Ethan";

console.log(original.name); // "David"
  • 使用 { ...物件 } 語法,可以複製第一層屬性的資料。
  • 適用於「只有一層」的物件。
  • 修改 copy 不會影響到 original,彼此獨立。

🧠 就像你影印了一張紙表單出來,你可以在影印本上改名字,不會動到原件。

展開運算子的限制:無法處理巢狀資料

const original = { info: { score: 100 } };
const copy = { ...original };

copy.info.score = 50;

console.log(original.info.score); // 50 ❗️會一起變
  • 雖然第一層 info 是複製的,但 info 的內容(score)還是指向同一記憶體位址。
  • 這種情況下,兩者還是共享同一份「內部資料」。

深層複製(Deep Copy)—— 解決巢狀同步問題

const obj = { info: { score: 100 } };
const deepCopy = JSON.parse(JSON.stringify(obj));

deepCopy.info.score = 50;

console.log(obj.info.score); // 100 ✅ 不受影響
  • 使用 JSON.parse(JSON.stringify(...)) 可以將物件轉成字串再重建,達到深層複製效果。
  • 適用於資料結構中沒有函式、undefinedSymbolDate 等特殊值時。

🧠 像是把一棟房子的所有東西都照著蓋一棟新的——包含家具、樓層結構、每一個房間的內容都重建一次。

延伸技巧:進階使用者可用工具函式庫

如果你需要更彈性、保留特殊資料型別(例如 DateMapSet)的深層複製,推薦使用:

  • lodash.cloneDeep()
  • structuredClone()(現代瀏覽器內建)

生活情境重新比喻:值 vs. 參考,就像「影本 vs. 雲端文件」

值類型(Value Type)就像「影印文件」

你請兩個同事來拿履歷資料:

const resumeA = "我是一份影印履歷";
const resumeB = "我是一份影印履歷";
  • A 和 B 拿到的內容一模一樣,但彼此是兩張紙
  • A 把自己的紙塗鴉,不會影響 B 的那張。
  • 系統比較這兩張時(===),看的是內容,只要一樣就算相等。

🔍 這就是值類型:變數儲存的是「資料本身」,獨立、純粹、可比。

參考類型(Reference Type)就像「共用 Google 文件的連結」

你不是列印紙,而是給兩人同一個 Google 文件的連結

const doc = { content: "我是一份雲端履歷" };
const personA = doc;
const personB = doc;
  • 他們連到同一份文件,改任何地方,另一人也會看到。
  • 系統比較這兩個變數(===),會是 true,因為是同一個記憶體位置

🔍 這是參考類型:變數存的是「資料的地址」,而不是資料本身。

❗️ 但如果你開了兩個長得一樣的 Google 文件(不同網址)

const docA = { content: "我是一份雲端履歷" };
const docB = { content: "我是一份雲端履歷" };

console.log(docA === docB); // false
  • 即使他們的內容一模一樣,但網址不同,屬於兩份獨立文件
  • 改了其中一份,另一份不會變
  • 系統判斷時會說:這是兩個不同的物件(記憶體位置不同)。

🧠 換句話說:「你可以有兩份內容完全一樣的文件,但它們還是兩個不同的檔案。」

整體整理:值 vs. 參考的生活對照表

概念值類型(Value)參考類型(Reference)
類比紙本履歷(影印件)雲端文件(Google Docs)
儲存方式存放資料本身存放文件網址(記憶體位置)
比較依據比對履歷內容比對是否是「同一份文件」(同一個網址)
修改行為改一份不影響另一份改了就會同步變動(共用同一份)
相同內容時視為相等(如果值一樣)還是視為不同(如果不是同一份物件)
避免同步方式每人發一份影印本建立一個新 Google 文件,再傳新連結給另一人

為什麼學這個很重要?

當你進一步學習 React、Vue 或寫 API、管理狀態時,如果搞不清楚資料是值還是參考,可能會:

  • 不小心改到不該動的資料(例如原本的 state)
  • 寫出「看不見變動來源」的 bug
  • 比較資料時一直得到 false,以為值不一樣

只要記住這個簡單口訣:

🔑 原始類型看內容,物件類型看記憶體位置。

總結與對照表

特性值類型參考類型
儲存方式直接儲存資料本體儲存記憶體參考(指標)
比較方式比較資料內容比較是否為同一個記憶體位置
複製後是否連動不會預設會連動
範例10, "hi", true{}, [], function(){}

Similar Posts