深拷贝与浅拷贝:用 JSON.parse 和 stringify 有什么坑?
2025-03-19
需要复制一份对象改着玩,又不想动原数据?很多人第一反应是 JSON.parse(JSON.stringify(obj))。对纯 JSON 数据它确实快,但一旦对象里混了 Date、函数或循环引用,就会静默丢字段或直接抛错。
浅拷贝 vs 深拷贝
浅拷贝(展开运算符 {...obj}、Object.assign)只复制第一层,嵌套对象仍共享引用。
深拷贝要求嵌套层级也是新对象;JSON 往返法本质是「序列化再反序列化」,只保留 JSON 能表达的类型。
JSON 往返会丢什么?
undefined、函数、Symbol 键会被丢弃;Date 变成 ISO 字符串而非 Date 实例。
Map、Set、RegExp 等会变成空对象或字符串;NaN、Infinity 在 JSON 里本就不合法,会变成 null 或导致序列化失败。
const o = { d: new Date(), fn: () => 1, u: undefined };
JSON.parse(JSON.stringify(o)); // { d: "2026-06-10T..." } // fn、u 消失循环引用:直接抛错
obj.self = obj 时,JSON.stringify 抛出 Converting circular structure to JSON。
这不是 bug,而是 JSON 本身不能表示图结构;需要 structuredClone 或手写遍历 + WeakMap。
structuredClone 更适合什么?
现代浏览器与 Node 17+ 内置 structuredClone,保留 Date、Map、Set、RegExp,并支持循环引用。
函数、DOM 节点、WeakMap 仍无法克隆;带 class 方法的对象会丢原型链。
怎么选?
纯 API 响应 / 配置 JSON:JSON 往返仍是最轻量的深拷贝。
含 Date、Map 或环:用 structuredClone;复杂业务对象考虑 lodash.cloneDeep。
拷贝前先 JSON.stringify 预览:若输出已变形,说明不该用这条路径。