深拷贝创建完全独立的新对象,浅拷贝仅复制第一层引用;JSON方法不可靠,Lodash cloneDeep 更稳妥,structuredClone 是未来方向但兼容性有限。

深拷贝会创建一个全新对象,所有嵌套层级的属性值都独立复制;浅拷贝只复制第一层引用,嵌套对象仍共享内存地址。直接用 JSON.parse(JSON.stringify(obj)) 看似简单,但会丢函数、undefined、Date、RegExp、循环引用等,不能算真正可靠的深拷贝。
浅拷贝只断开第一层引用
常见操作如 Object.assign()、展开运算符 {...obj}、Array.prototype.slice() 都属于浅拷贝。它们对嵌套对象不做递归处理,修改子对象属性会影响原对象。
典型错误现象:
const a = { name: 'Alice', info: { age: 25 } };
const b = { ...a };
b.info.age = 30;
console.log(a.info.age); // 输出 30 —— 原对象被意外修改
适用场景:仅需隔离顶层字段,且确定结构扁平、不含引用类型值时可用。
立即学习“Java免费学习笔记(深入)”;
注意事项:
-
Object.assign()会忽略undefined和null源对象,但不会报错 - 展开运算符对
Map、Set、Date等内置对象无效,仅适用于普通对象和数组 - 浅拷贝后,
instanceof判断仍成立,但原型链未被复制(这点常被忽略)
手写递归深拷贝要处理边界情况
基础递归能解决多数嵌套对象/数组,但必须显式判断类型并跳过不可枚举、原型属性、循环引用等。
关键判断逻辑:
- 用
typeof+Array.isArray()区分数组、普通对象、基本类型 - 对
null单独处理(typeof null === 'object'是 JS 历史 bug) - 遇到
Date、RegExp等特殊对象,需用构造函数新建实例 - 必须用
WeakMap记录已遍历对象,防止循环引用导致栈溢出
简化版示例(不覆盖所有类型,但体现核心思路):
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
const cloned = Array.isArray(obj) ? [] : {};
seen.set(obj, cloned);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key], seen);
}
}
return cloned;
}
Lodash 的 cloneDeep 为什么更稳妥
它内部做了大量类型识别与特殊处理,比如:
- 检测
Buffer、Map、Set、TypedArray并分别克隆 - 对
function默认不拷贝(保留引用),也可通过自定义迭代器控制 - 自动处理循环引用,返回正确结构而非报错或死循环
- 兼容 ES3+ 环境,不依赖
WeakMap(降级用Array模拟)
性能上,lodash.cloneDeep 比手写递归略慢,但稳定性远高于 DIY 方案。如果项目已引入 Lodash,直接用它比反复修 bug 更省心。
注意:import { cloneDeep } from 'lodash-es' 比全量导入更轻量,Tree-shaking 可生效。
现代方案:structuredClone 是未来方向但兼容性有限
Chrome 98+、Firefox 94+、Edge 98+ 支持原生 structuredClone,它基于 HTML 标准的结构化克隆算法,能正确处理 Date、Map、Set、ArrayBuffer 等,且自动处理循环引用。
但它不支持 function、undefined、Symbol、RegExp(会抛错),也不能克隆带有原型方法的对象(只保留可枚举自有属性)。
使用前务必检查运行环境:
if (typeof structuredClone === 'function') {
const copy = structuredClone(original);
} else {
// fallback to lodash or custom impl
}
目前最现实的做法是:开发环境用 structuredClone,生产环境按需 fallback。别把它当成“万能替代”,尤其当数据含 RegExp 或自定义类实例时,依然会失败。
真正麻烦的从来不是“怎么写个深拷贝”,而是“你到底要拷什么”——函数要不要?Symbol 键要不要?原型链要不要?循环引用出现频率高不高?选方案前,先看清数据的真实构成。











