在 JavaScript 中,「Call by Value(傳值)」與「Call by Reference(傳參考)」是常被提起但容易誤解的觀念,特別是在處理物件與陣列時。
✅ JS 中的參數傳遞:Call by Value 還是 Call by Reference?
🔑 關鍵理解:
👉 JavaScript 中的所有參數傳遞都是「傳值」(Call by Value)!
但當你傳的是參考型別的變數(如物件、陣列),你傳的是「那個記憶體位址的值」,所以看起來像「傳參考」。
🧠 1. Call by Value(傳值)
- 適用於:原始型別(Primitive types)
- 傳入函式時,複製的是值本身
- 在函式內改變變數,不會影響外部變數
1 | function changeValue(x) { |
🧠 2. Call by Reference(表面上是傳參考)
- 適用於:參考型別(Object, Array, Function 等)
- 傳入的是該物件的記憶體參考值(位址)
- 在函式內修改物件內容,會影響外部原始物件
1 | function modify(obj) { |
❗ 但是注意:重新賦值無法改變原物件
1 | function change(obj) { |
🔍 為什麼?
因為obj = {...}
是在函式內部重新指定一個新的記憶體參考,不會影響原來的參考地址本身。
✅ 總結比較表格
類型 | 傳遞方式 | 是否可改變原值 | 說明 |
---|---|---|---|
原始型別(數字、字串等) | Call by Value | ❌ 否 | 傳入函式時複製值,函式內更改不影響外部 |
參考型別(物件、陣列等) | Call by Value(但是傳 reference 的值) | ✅ 可以改內容 ❌ 不能重新指定 |
傳的是記憶體位置的值,可改內容,但不能改記憶體位置本身 |
🧪 題目挑戰(幫助理解)
❓這段程式碼會輸出什麼?
1 | function update(arr) { |
✅ 答案:
1 | [0, 4] ✅ |
因為 arr.push(4)
是修改原陣列內容(有效)
而 arr = [1, 2]
是重新指定變數,不會影響外部的 list
📌 總結一句話:
JavaScript 是 Call by Value,但當你傳的是物件或陣列時,你是傳了參考的值,所以函式能改內容但不能換物件本身。
最佳實踐
使用情境描述 | 是否建議拷貝 | 推薦方式 | 原因 / 說明 |
---|---|---|---|
僅讀取物件資料,不會修改 | ❌ 不需要 | 直接傳入 | 沒有副作用,效能較佳 |
函式內部會修改第一層資料(但不應影響原始資料) | ✅ 建議 | 淺拷貝:{ ...obj } |
防止污染原始資料(僅拷貝第一層) |
函式內會修改巢狀屬性,如 obj.user.age = 30 |
✅ 必須 | 深拷貝:cloneDeep(obj) |
避免巢狀結構共用參考,防止原始物件被改變 |
僅需要物件中部分資料(如一個 key) | ✅ 建議 | 解構取值:function({ id }) |
只傳入需要的屬性,可避免整包物件傳入、避免意外修改 |
處理複雜資料、可能有循環結構或特殊型別(如 function) | ✅ 建議 | lodash.cloneDeep() 或自定函式 |
避免 JSON.stringify 限制,保留完整資料類型 |
效能敏感、不希望多餘拷貝,但又需保護資料 | ✅ 可選 | 防變更操作 + 明確文件註記 | 可避免不必要的拷貝成本,但需確保開發者不誤改資料 |
需要修改參數,但只希望在函式內有效(不污染外部) | ✅ 必須 | 深拷貝 + 修改副本 | 保持資料純淨,函式有單一責任,易於除錯與維護 |
補充
原則一:不確定資料是否會被改,寧可先拷貝
原則二:若資料大且拷貝成本高,須權衡性能與安全
原則三:資料敏感(如設定檔、使用者狀態)務必拷貝