深拷贝
深拷贝的定义、常见实现方式和与浅拷贝、structuredClone 的关系。
#type / concept
#status / growing
#tech / dev / frontend
[!info] related notes
- 相关笔记: structuredClone, 浅拷贝 vs 深拷贝, JSON 序列化
深拷贝
一句话定义
深拷贝递归复制对象的所有层级引用,产出的新对象与原对象完全独立,修改任一方不影响另一方。
核心机制
浅拷贝只复制第一层:嵌套对象仍然是共享引用。深拷贝遍历每一层,遇到对象/数组就递归创建新副本。
原对象: { a: 1, b: { c: 2 } }
浅拷贝: { a: 1, b: ──┐ }
└──→ { c: 2 } (同一个!)
深拷贝: { a: 1, b: ──┐ }
└──→ { c: 2 } (独立副本)
方法一:JSON.parse + JSON.stringify
const original = { name: 'Alice', scores: [90, 85], meta: { age: 25 } };
const cloned = JSON.parse(JSON.stringify(original));
cloned.scores.push(100);
console.log(original.scores); // [90, 85] — 不受影响
局限性:
- 丢失
undefined、Function、Symbol键值 Date变为 ISO 字符串(不再是 Date 实例)RegExp、Map、Set变为空对象{}- 循环引用直接报错
TypeError: Converting circular structure to JSON BigInt直接报错TypeError: Do not know how to serialize a BigInt
方法二:structuredClone(推荐)
const original = {
date: new Date(),
data: new Map([['key', 'value']]),
};
const cloned = structuredClone(original);
console.log(cloned.date instanceof Date); // true
console.log(cloned.data instanceof Map); // true
优点:原生支持 Date、RegExp、Map、Set、ArrayBuffer、Blob、循环引用。
局限:不能克隆 Function、DOM 节点、Proxy,会抛出 DataCloneError。
方法三:手写递归
function deepClone(obj, seen = new WeakMap()) {
// 基本类型 / null
if (obj === null || typeof obj !== 'object') return obj;
// 循环引用
if (seen.has(obj)) return seen.get(obj);
// Date
if (obj instanceof Date) return new Date(obj);
// RegExp
if (obj instanceof RegExp) return new RegExp(obj);
// Map
if (obj instanceof Map) {
const map = new Map();
seen.set(obj, map);
obj.forEach((v, k) => map.set(deepClone(k, seen), deepClone(v, seen)));
return map;
}
// Set
if (obj instanceof Set) {
const set = new Set();
seen.set(obj, set);
obj.forEach(v => set.add(deepClone(v, seen)));
return set;
}
// Array / Object
const clone = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
seen.set(obj, clone);
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], seen);
}
return clone;
}
性能对比
| 方法 | 循环引用 | 类型支持 | 性能 | 环境要求 |
|---|---|---|---|---|
JSON.parse/stringify | 报错 | 基本类型 + 数组 + 普通对象 | 快 | 所有环境 |
structuredClone | 支持 | 多数内建类型 | 中等 | 浏览器 / Node 17+ |
| 手写递归 | 需手动处理 | 可自定义 | 慢(深度大时) | 所有环境 |
lodash _.cloneDeep | 支持 | 最全面 | 中等 | 需引入依赖 |
边界与常见误解
- JSON 方案”够用”是有条件的:如果数据中只有基本类型和普通对象/数组,JSON 方案最快且最简洁。
- structuredClone 不是万能的:函数、DOM 节点、类实例上的方法全部丢失。
- 性能:对大对象,
JSON.parse(JSON.stringify())通常比structuredClone快 2-5 倍,因为 JSON 引擎是 C++ 实现的。 - 类实例:
structuredClone和 JSON 方案都只拷贝数据,不保留原型链上的方法。