[Medium] 📄 Primitive vs Reference Types
1. What are Primitive Types and Reference Types?
什麼是原始型別(Primitive Types)和參考型別(Reference Types)?
JavaScript 的資料型別分為兩大類:原始型別和參考型別。它們在記憶體儲存方式和傳遞行為上有本質的差異。
原始型別(Primitive Types)
特點:
- 儲存在**堆疊(Stack)**中
- 傳遞時複製值本身(Call by Value)
- 不可變的(Immutable)
包含 7 種:
// 1. String(字串)
const str = 'hello';
// 2. Number(數字)
const num = 42;
// 3. Boolean(布林值)
const bool = true;
// 4. Undefined
let undef;
// 5. Null
const n = null;
// 6. Symbol(ES6)
const sym = Symbol('unique');
// 7. BigInt(ES2020)
const bigInt = 9007199254740991n;
參考型別(Reference Types)
特點:
- 儲存在**堆積(Heap)**中
- 傳遞時複製參考(記憶體地址)(Call by Reference)
- 可變的(Mutable)
包含:
// 1. Object(物件)
const obj = { name: 'John' };
// 2. Array(陣列)
const arr = [1, 2, 3];
// 3. Function(函式)
const func = function () {};
// 4. Date
const date = new Date();
// 5. RegExp
const regex = /abc/;
// 6. Map, Set, WeakMap, WeakSet (ES6)
const map = new Map();
const set = new Set();
2. Call by Value vs Call by Reference
傳值(Call by Value)vs 傳址(Call by Reference)
傳值(Call by Value)- 原始型別
行為:複製值本身,修改副本不影響原值。
// 原始型別:傳值
let a = 10;
let b = a; // 複製值
b = 20; // 修改 b
console.log(a); // 10(不受影響)
console.log(b); // 20
記憶體示意圖:
┌─────────┐
│ Stack │
├─────────┤
│ a: 10 │ ← 獨立的值
├─────────┤
│ b: 20 │ ← 獨立的值(複製後修改)
└─────────┘
傳址(Call by Reference)- 參考型別
行為:複製記憶體地址,兩個變數指向同一個物件。
// 參考型別:傳址
let obj1 = { name: 'John' };
let obj2 = obj1; // 複製記憶體地址
obj2.name = 'Jane'; // 透過 obj2 修改
console.log(obj1.name); // 'Jane'(被影響!)
console.log(obj2.name); // 'Jane'
console.log(obj1 === obj2); // true(指向同一個物件)
記憶體示意圖:
┌─────────┐ ┌──────────────────┐
│ Stack │ │ Heap │
├─────────┤ ├──────────────────┤
│ obj1 ───┼───────────────────>│ { name: 'Jane' } │
├─────────┤ │ │
│ obj2 ───┼───────────────────>│ (同一個物件) │
└─────────┘ └──────────────────┘
3. Common Quiz Questions
常見測驗題
題目 1:原始型別的傳遞
function changeValue(x) {
x = 100;
console.log('函式內 x:', x);
}
let num = 50;
changeValue(num);
console.log('函式外 num:', num);
點擊查看答案
// 函式內 x: 100
// 函式外 num: 50
解釋:
num是原始型別(Number)- 傳入函式時複製值,
x和num是兩個獨立的變數 - 修改
x不會影響num
// 執行流程
let num = 50; // Stack: num = 50
changeValue(num); // Stack: x = 50(複製)
x = 100; // Stack: x = 100(只修改 x)
console.log(num); // Stack: num = 50(不受影響)
題目 2:參考型別的傳遞
function changeObject(obj) {
obj.name = 'Changed';
console.log('函式內 obj.name:', obj.name);
}
let person = { name: 'Original' };
changeObject(person);
console.log('函式外 person.name:', person.name);
點擊查看答案
// 函式內 obj.name: Changed
// 函式外 person.name: Changed
解釋:
person是參考型別(Object)- 傳入函式時複製記憶體地址
obj和person指向同一個物件- 透過
obj修改物件內容,person也會被影響
// 記憶體示意
let person = { name: 'Original' }; // Heap: 建立物件 @0x001
changeObject(person); // Stack: obj = @0x001(複製地址)
obj.name = 'Changed'; // Heap: @0x001.name = 'Changed'
console.log(person.name); // Heap: @0x001.name(同一個物件)
題目 3:重新賦值 vs 修改屬性
function test1(obj) {
obj.name = 'Modified'; // 修改屬性
}
function test2(obj) {
obj = { name: 'New Object' }; // 重新賦值
}
let person = { name: 'Original' };
test1(person);
console.log('A:', person.name);
test2(person);
console.log('B:', person.name);
點擊查看答案
// A: Modified
// B: Modified(不是 'New Object'!)
解釋:
test1:修改屬性
function test1(obj) {
obj.name = 'Modified'; // ✅ 修改原物件的屬性
}
// person 和 obj 指向同一個物件,所以會被修改
test2:重新賦值
function test2(obj) {
obj = { name: 'New Object' }; // ❌ 只改變 obj 的指向
}
// obj 現在指向新物件,但 person 仍指向原物件
記憶體示意圖:
// test1 之前
person ────> { name: 'Original' }
obj ────> { name: 'Original' } (同一個)
// test1 之後
person ────> { name: 'Modified' }
obj ────> { name: 'Modified' } (同一個)
// test2 執行
person ────> { name: 'Modified' } (不變)
obj ────> { name: 'New Object' } (新物件)
// test2 結束後
person ────> { name: 'Modified' } (仍然不變)
// obj 被銷毀,新物件被垃圾回收
題目 4:陣列的傳遞
function modifyArray(arr) {
arr.push(4);
console.log('1:', arr);
}
function reassignArray(arr) {
arr = [5, 6, 7];
console.log('2:', arr);
}
let numbers = [1, 2, 3];
modifyArray(numbers);
console.log('3:', numbers);
reassignArray(numbers);
console.log('4:', numbers);
點擊查看答案
// 1: [1, 2, 3, 4]
// 3: [1, 2, 3, 4]
// 2: [5, 6, 7]
// 4: [1, 2, 3, 4]
解釋:
modifyArray:修改原陣列內容,numbers被影響reassignArray:只改變參數的指向,numbers不受影響