JavaScript structuredClone深拷贝与JSON序列化的区别
在JavaScript开发中,经常需要复制一个对象。如果直接赋值,只是复制了引用,修改新对象会影响原对象。为了得到一个完全独立的副本,我们需要“深拷贝”。目前最常用的两种方案是传统的 JSON.parse(JSON.stringify()) 和现代的 structuredClone()。
1. 验证基础数据类型的拷贝
首先测试两种方法在处理简单对象时的表现。
打开浏览器的开发者工具控制台。输入以下代码并执行:
const original = { name: "Alice", score: 90 };
const jsonCopy = JSON.parse(JSON.stringify(original));
const cloneCopy = structuredClone(original);
// 修改原对象
original.name = "Bob";
// 查看结果
console.log(jsonCopy.name); // 输出: "Alice"
console.log(cloneCopy.name); // 输出: "Alice"
观察输出结果,两者都成功保留了 Alice,说明在处理纯数据对象时,两者都能实现基本的深拷贝。
2. 处理特殊对象类型(Date 与 RegExp)
当对象中包含日期、正则等特殊类型时,差异就会显现。
定义一个包含 Date 和 RegExp 的对象:
const specialObj = {
date: new Date('2023-10-01'),
regex: /test/gi
};
使用 JSON 序列化方法进行拷贝:
const jsonResult = JSON.parse(JSON.stringify(specialObj));
console.log(jsonResult.date); // 输出: 字符串 "2023-10-01T00:00:00.000Z"
console.log(jsonResult.regex); // 输出: 空对象 {}
console.log(jsonResult.date instanceof Date); // 输出: false
使用 structuredClone 方法进行拷贝:
const cloneResult = structuredClone(specialObj);
console.log(cloneResult.date); // 输出: Date 对象
console.log(cloneResult.regex); // 输出: RegExp 对象
console.log(cloneResult.date instanceof Date); // 输出: true
对比结果,JSON 方法把时间变成了字符串,把正则变成了空对象;而 structuredClone 完美保留了它们的原始类型。
3. 解决循环引用问题
循环引用是指对象的属性引用了对象本身,这是深拷贝中常见的“坑”。
创建一个具有循环引用的对象:
const circularObj = { name: "Loop" };
circularObj.self = circularObj; // 属性指向自己
尝试使用 JSON 序列化:
try {
JSON.parse(JSON.stringify(circularObj));
} catch (e) {
console.error("JSON报错:", e.message);
}
// 控制台输出: "JSON报错: Converting circular structure to JSON"
切换到 structuredClone:
const clonedCircular = structuredClone(circularObj);
console.log(clonedCircular.self === clonedCircular); // 输出: true
注意,structuredClone 成功拷贝了对象,并且新对象内部的循环引用依然指向新对象自身,没有报错。
4. 拷贝机制的底层逻辑
为了更直观地理解两者在处理复杂结构时的差异,可以参考以下流程逻辑:
转为普通对象或字符串] B -- "Date / RegExp / Map / Set" --> E[structuredClone] E --> F[保留原始类型] A --> G{包含循环引用?} G -- 是 --> C C --> H[抛出错误] G -- 是 --> E E --> I[拷贝成功]
5. 处理不支持的数据类型(函数与 Symbol)
两者也都有无法处理的“盲区”,主要针对函数和 Symbol。
定义包含函数和 Symbol 的对象:
const funcObj = {
id: Symbol("id"),
sayHello: function() { console.log("Hi"); }
};
分别运行两种拷贝方式:
// JSON 序列化
const jsonFunc = JSON.parse(JSON.stringify(funcObj));
console.log(jsonFunc.id); // 输出: undefined
console.log(jsonFunc.sayHello); // 输出: undefined
// structuredClone
try {
structuredClone(funcObj);
} catch (e) {
console.error("structuredClone报错:", e.message);
}
// 控制台输出: "structuredClone报错: #<Object> could not be cloned"
注意,JSON 会直接忽略函数和 Symbol(变为 undefined),而 structuredClone 会直接抛出错误。
6. 核心差异对比表
为了方便快速查阅,以下是两种方法的详细对比:
| 特性 | JSON 序列化 | structuredClone |
|---|---|---|
| 基本类型支持 | 支持 | 支持 |
| 对象/数组 | 支持 | 支持 |
| Date (日期) | 转为字符串 | 保留 Date 对象 |
| RegExp (正则) | 转为空对象 {} |
保留 RegExp 对象 |
| Map / Set | 转为普通对象 {} |
保留 Map / Set |
| 循环引用 | 抛出错误 | 支持拷贝 |
| Function (函数) | 忽略 (变为 undefined) | 抛出错误 |
| Symbol | 忽略 (变为 undefined) | 抛出错误 |
| 执行环境 | 极高 (所有浏览器) | 较高 (现代浏览器, Node 17+) |
7. 选择合适的方法
根据项目需求和数据结构,选择正确的方案:
- 如果数据结构简单,只包含数字、字符串、数组或普通对象,且不需要考虑极老的浏览器环境,使用
JSON.parse(JSON.stringify())是完全没问题的。 - 如果数据中包含
Date、Map、Set、RegExp或存在循环引用,必须使用structuredClone()。 - 如果数据中包含函数或 Symbol,且必须保留它们,这两种方法都无法满足需求,需要使用 Lodash 的
_.cloneDeep()等第三方库,或者手写递归拷贝函数。
运行以下代码判断当前环境是否支持 structuredClone:
if (typeof structuredClone === 'function') {
console.log("当前环境支持 structuredClone");
} else {
console.log("当前环境不支持 structuredClone");
}
暂无评论,快来抢沙发吧!