浅拷贝只复制第一层引用,Object.assign()和展开运算符均属此类,不递归处理嵌套对象,修改深层属性会影响原对象;深拷贝中JSON方法有诸多限制,structuredClone()是目前最简且较可靠的原生方案。

浅拷贝只复制第一层引用,Object.assign() 和展开运算符都属于这一类
浅拷贝不会递归复制嵌套对象,只是把顶层属性的值(对对象来说是内存地址)复制一份。这意味着修改嵌套对象的属性,原对象和拷贝对象会互相影响。
常见写法:
const obj = { a: 1, b: { c: 2 } };
const shallow = { ...obj }; // 或 Object.assign({}, obj)
shallow.b.c = 99;
console.log(obj.b.c); // 输出 99 —— 原对象被意外修改
适用场景:确定对象是单层结构,或你本意就是共享内部引用(比如配置合并)。
注意点:
立即学习“Java免费学习笔记(深入)”;
-
Object.assign()会触发 setter,而展开运算符不会 - 两者都无法拷贝
Symbol键(除非用Reflect.ownKeys()配合手动遍历) - 不处理
undefined、function、Date、RegExp等特殊类型,仅按普通属性复制
深拷贝要警惕循环引用和不可序列化值,JSON.parse(JSON.stringify()) 最快但限制最多
这个组合看似简洁,实际是“伪深拷贝”:它先序列化再反序列化,过程中会丢失大量信息。
典型问题:
-
undefined、function、Symbol、BigInt被忽略或转为null -
Date变成字符串,RegExp变成空对象{} - 遇到循环引用直接抛错:
TypeError: Converting circular structure to JSON - 无法保留原型链(拷贝结果总是
Object.prototype)
示例:
const obj = { d: new Date(), r: /abc/, f() {} };
const broken = JSON.parse(JSON.stringify(obj));
console.log(broken.d); // "2024-01-01T00:00:00.000Z"(字符串,不是 Date 实例)
console.log(broken.r); // {}
console.log(typeof broken.f); // "undefined"
只适合临时处理纯数据对象(如 API 响应体),且确认不含上述类型。
真正可靠的深拷贝得自己控制序列化逻辑,structuredClone() 是目前最简方案
structuredClone() 是现代浏览器(Chrome 98+、Firefox 97+、Safari 16.4+)和 Node.js 17.0+(需启用 --experimental-structured-cloning)原生支持的深拷贝 API,能正确处理 Date、RegExp、Map、Set、ArrayBuffer、TypedArray 和循环引用。
使用方式极简:
const original = { x: new Date(), y: new Set([1, 2]), z: {} };
original.z.self = original; // 循环引用
const deep = structuredClone(original);
限制仍存在:
- 不支持
function、undefined、Symbol、BigInt(遇到就报错:DataCloneError) - 不能拷贝带有私有字段的类实例(
#field) - Node.js 中默认未启用,需命令行参数或检查运行时环境
兼容性差时,可降级到 lodash.cloneDeep(),但它体积大、依赖多,且对自定义类支持弱。
性能差异明显:浅拷贝最快,structuredClone() 次之,手写递归最慢也最危险
在 10k 层级嵌套对象测试中(Chrome 125):
-
{...obj}:约 0.02ms -
structuredClone(obj):约 0.8ms -
JSON.parse(JSON.stringify(obj)):约 1.3ms(且失败率高) - 手写递归深拷贝(含循环检测):约 3.5ms,易栈溢出或漏判边界
关键提醒:
不要为了“看起来安全”无脑上深拷贝。90% 的场景里,你根本不需要深拷贝——比如 React 中用 useState 更新对象,用函数式更新 + 展开运算符就够了;Redux Toolkit 的 createReducer 内部已用 Immer 处理了不可变更新。
真正该花时间的,是明确你的数据是否会被多处修改、是否跨线程(Worker)、是否含特殊类型——这些决定了你该停在哪一层拷贝。











