avatar
Published on

JS中JSON.parse(JSON.stringify())深拷贝的缺点

JS中JSON.parse(JSON.stringify())深拷贝的缺点

JS中JSON.parse(JSON.stringify())深拷贝的缺点

推荐使用 lodash.cloneDeep 实现可靠深拷贝(避坑 JSON.parse/stringify)

// 推荐按需引入(仅加载 cloneDeep 模块)
import cloneDeep from 'lodash/cloneDeep';

// 用法示例
const original = { name: '测试', list: [1, 2, 3] };
const cloned = cloneDeep(original);

很多开发者会用 JSON.parse(JSON.stringify()) 临时实现深拷贝,但它存在诸多局限性,仅适用于纯 JSON 结构数据。以下通过具体案例说明其问题,同时补充更全面的避坑指南。

一、案例数据(覆盖常见数据类型)

const testObj = {
  basic: '纯字符串数据',
  date: new Date('2025-01-01'), // 日期类型
  reg: new RegExp(/\w+/, 'g'), // 正则类型
  func: () => console.log('测试函数'), // 函数
  error: new Error('自定义错误'), // 错误对象
  symbol: Symbol('唯一标识'), // Symbol
  undef: undefined, // undefined
  nullVal: null, // null
  nan: NaN, // 非数字
  infinity: Infinity, // 无穷大
  bigInt: 9007199254740991n, // BigInt 类型
  nested: { a: 1, b: [2, 3] } // 嵌套对象
};

二、JSON.parse/stringify 的核心问题

1. 数据丢失/类型异常

执行拷贝后,多种类型会出现丢失或转义问题:

const faultyClone = JSON.parse(JSON.stringify(testObj));
console.log(faultyClone);

实际结果:

  • 函数 func、Symbol symbolundefined 直接丢失
  • 日期 date 转为字符串("2024-12-31T16:00:00.000Z"),丢失日期类型
  • 正则 reg、错误对象 error 变为空对象 {}
  • NaNInfinity 转为 null
  • BigInt 直接报错(TypeError: Do not know how to serialize a BigInt

2. 循环引用直接报错

若对象存在循环引用(A 引用 B,B 引用 A),拷贝会直接抛出异常:

const circleObj = {};
circleObj.self = circleObj; // 循环引用:自身引用自身

// 报错:Converting circular structure to JSON
JSON.parse(JSON.stringify(circleObj));

lodash.cloneDeep 可正常处理循环引用,不会报错且拷贝完整。

3. 构造函数丢失

自定义构造函数创建的实例,拷贝后会丢失原型链和 constructor,变为普通对象:

// 自定义构造函数
function User(name) {
  this.name = name;
  this.sayHi = () => console.log(`Hi, ${this.name}`);
}

const user = new User('张三');
const clonedUser = JSON.parse(JSON.stringify(user));

console.log(user instanceof User); // true(原始实例)
console.log(clonedUser instanceof User); // false(变为普通对象)
console.log(clonedUser.sayHi); // undefined(方法丢失)

4. 特殊值处理异常

除了常见类型,以下特殊场景也会出问题:

  • 数组中的 undefined 会转为 null[1, undefined, 3][1, null, 3]
  • 正则的 flags(如全局匹配 g)会丢失
  • 错误对象的 stack 调用栈信息丢失

三、两种方案对比表

特性/场景JSON.parse(JSON.stringify())lodash.cloneDeep
纯 JSON 数据(字符串、数字、嵌套对象)支持(正常拷贝)支持(正常拷贝)
日期、正则、函数、Symbol不支持(丢失/转义)支持(完整拷贝)
循环引用对象不支持(直接报错)支持(正常处理)
自定义构造函数实例不支持(丢失原型)支持(保留原型)
BigInt、NaN、Infinity不支持(报错/转义)支持(正常保留)
嵌套层级极深的对象可能栈溢出优化较好,不易溢出

四、使用建议

  1. 仅纯 JSON 数据:可临时使用 JSON.parse(JSON.stringify()),但需确保无特殊类型
  2. 生产环境/复杂数据:优先使用 lodash.cloneDeep,兼顾可靠性和兼容性
  3. 轻量场景(无循环引用):也可使用 structuredClone API(浏览器原生支持,无需依赖),但不支持函数、Symbol 等类型
// 原生 structuredClone 示例(无依赖,有限支持)
const nativeClone = structuredClone(testObj);
// 注意:不支持函数、Symbol、循环引用,仅适用于部分场景

总结

JSON.parse(JSON.stringify()) 是"临时方案",存在诸多隐式坑;为保证代码健壮性,尤其是处理复杂数据时,推荐使用 lodash.cloneDeep 这类成熟工具方法。