在 JavaScript 中,深淺拷貝是處理對象復制的重要概念。它們的核心區別在于對 引用類型數據 的處理方式,理解這一點對避免程序中的意外數據污染至關重要。
一、核心概念解析
1. 基本類型 vs 引用類型
-
基本類型:
Number
,String
,Boolean
,null
,undefined
,Symbol
,BigInt
直接存儲在棧內存中,復制時創建獨立副本。 -
引用類型:
Object
,Array
,Function
,Date
,RegExp
等
棧內存存儲指針,堆內存存儲實際數據,復制時默認只復制指針。
2. 賦值 vs 淺拷貝 vs 深拷貝
操作類型 | 特點 | 是否共享引用 |
---|---|---|
直接賦值 | 復制指針,完全共享數據 | ? |
淺拷貝 | 創建新對象,但嵌套引用類型仍共享 | 嵌套層級共享 ? |
深拷貝 | 完全獨立的新對象,所有層級不共享 | ? |
二、淺拷貝實現方案
1. 手動實現
function shallowCopy(obj) {if (typeof obj !== 'object' || obj === null) return obj;const newObj = Array.isArray(obj) ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = obj[key]; // 僅復制第一層}}return newObj;
}
2. 內置方法
-
Object.assign()
const obj = { a: 1, b: { c: 2 } }; const copy = Object.assign({}, obj);
-
展開運算符
...
const arr = [1, { x: 2 }]; const arrCopy = [...arr];
3. 特性驗證
const original = { a: 1, b: { c: 2 } };
const copy = shallowCopy(original);copy.b.c = 999; // 修改嵌套對象
console.log(original.b.c); // 999 → 數據被污染
三、深拷貝實現方案
1. JSON 序列化法
const deepCopyJSON = obj => JSON.parse(JSON.stringify(obj));
局限性:
- 無法處理
undefined
、Symbol
、函數 - 忽略原型鏈
- 不能解決循環引用
- 會破壞特殊對象(如
Date
變為字符串)
2. 遞歸實現(基礎版)
function deepClone(source) {if (source === null || typeof source !== 'object') return source;const target = Array.isArray(source) ? [] : {};for (let key in source) {if (source.hasOwnProperty(key)) {target[key] = deepClone(source[key]); // 遞歸復制}}return target;
}
3. 增強版深拷貝
處理復雜場景:
function enhancedDeepClone(source, map = new WeakMap()) {// 處理循環引用if (map.has(source)) return map.get(source);// 處理特殊對象if (source instanceof Date) return new Date(source);if (source instanceof RegExp) return new RegExp(source);const target = Array.isArray(source) ? [] : {};map.set(source, target);// 處理Symbol作為keyconst symKeys = Object.getOwnPropertySymbols(source);symKeys.forEach(symKey => {target[symKey] = enhancedDeepClone(source[symKey], map);});for (let key in source) {if (source.hasOwnProperty(key)) {target[key] = enhancedDeepClone(source[key], map);}}return target;
}
4. 第三方庫
- lodash 的
_.cloneDeep()
import _ from 'lodash'; const perfectCopy = _.cloneDeep(original);
四、性能與選擇策略
方法 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
淺拷貝 | 速度快、內存占用少 | 嵌套數據不安全 | 簡單對象結構 |
JSON法 | 簡單快捷 | 丟失特殊類型 | 無特殊類型的數據 |
遞歸深拷貝 | 完全控制 | 性能消耗大 | 復雜對象結構 |
第三方庫 | 功能完善 | 增加依賴 | 生產環境推薦 |
五、應用場景示例
-
狀態管理
Redux 的 reducer 中必須使用深拷貝保證狀態不可變性:function reducer(state, action) {return {...state,user: _.cloneDeep(action.payload) // 確保狀態獨立}; }
-
數據快照
保存歷史記錄時需完全獨立的數據副本:const gameState = { players: [{ score: 0 }], level: 1 }; const history = [deepClone(gameState)];
-
配置對象復用
修改配置模板不影響原始模板:const defaultConfig = { theme: 'dark', permissions: { read: true } }; const userConfig = deepClone(defaultConfig); userConfig.permissions.write = false;
六、常見誤區
-
誤以為
Object.assign()
是深拷貝
實際上它只能實現第一層深拷貝,嵌套對象仍是淺拷貝。 -
忽略循環引用問題
const obj = { a: 1 }; obj.self = obj; deepClone(obj); // 基礎實現會棧溢出
-
處理特殊對象不當
直接克隆Date
對象會丟失方法:const date = new Date(); const badCopy = JSON.parse(JSON.stringify(date)); // 變成字符串
掌握深淺拷貝的底層原理和實現方式,能幫助開發者根據實際需求選擇最優解決方案,在保證數據安全性的同時平衡性能消耗。對于日常開發,推薦優先使用成熟的工具庫(如 lodash),但在需要精細控制時,理解手寫實現原理至關重要。