深拷贝与浅拷贝:用 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..." }  // fnu 

循环引用:直接抛错

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 预览:若输出已变形,说明不该用这条路径。