做前端这么多年,JSON.parse(JSON.stringify(obj)) 这行代码你一定不陌生。
它简单、好用、随处可见------但也处处是坑:
- 函数?没了。
- undefined?没了。
- Symbol?没了。
- 循环引用?直接报错。
- Date、RegExp、Map、Set?统统变成你不认识的样子。
2022 年,浏览器终于推出了原生的深拷贝 API ------ structuredClone() 。它一度被称作"JSON 深拷贝的终结者"。
但现实是:它并不是 lodash.cloneDeep 的替代品,而是互补品。
一个处理纯数据,一个兜底带行为的数据。今天就把两者的边界讲清楚。
一、核心能力对比
根据 greatfrontend 链接1 链接2 整理的对比表:
| 值 / 场景 | structuredClone | Lodash cloneDeep |
|---|---|---|
| 嵌套对象 / 数组 | ✅ 克隆 | ✅ 克隆 |
| Date | ✅ 保留 Date | ✅ 保留 Date |
| RegExp | ✅ 保留 RegExp | ✅ 保留 RegExp |
| Map / Set | ✅ 克隆 | ✅ 克隆 |
| 循环引用 | ✅ 支持 | ✅ 支持 |
| 函数 / 方法 | ❌ 抛 DataCloneError | ✅ 按引用保留 |
| Symbol 键 | ❌ 不克隆 | ✅ 克隆 |
| 类实例原型链 | ❌ 丢失,变普通对象 | ✅ 保留原型链 |
| BigInt | ✅ 保留 | ✅ 保留 |
instanceof 检查 |
❌ 类实例克隆后 instanceof 失效 |
✅ 保持类实例身份 |
二、关键差异详解
1. 函数处理:最本质的分水岭
structuredClone 遇到函数直接抛异常:
js
scss
structuredClone({ fn: () => {} });
// ❌ DataCloneError: () => {} could not be cloned.
js
ini
const obj = { fn: () => console.log('hello') };
const clone = _.cloneDeep(obj);
clone.fn === obj.fn; // true(函数本身是同一引用)
这意味着:如果你的数据模型包含方法或回调函数,structuredClone 完全不可用,只能选 cloneDeep。
2. 原型链:类实例的命运不同
structuredClone 返回普通对象,原型链被剥离 :
js
javascript
class User {
constructor(name) { this.name = name; }
greet() { return `Hi, ${this.name}`; }
}
const u = new User('张三');
const cloned = structuredClone(u);
cloned instanceof User; // ❌ false
cloned.greet; // ❌ undefined
js
ini
const cloned = _.cloneDeep(u);
cloned instanceof User; // ✅ true
cloned.greet(); // ✅ 'Hi, 张三'
但注意:两者都不会重新执行构造函数 ,只在构造时生效的不变量不会在克隆对象上重新应用 。
3. Symbol 键的处理
structuredClone 不克隆 Symbol 键的属性 ,cloneDeep 则可以 。
js
ini
const sym = Symbol('key');
const obj = { [sym]: 'value' };
structuredClone(obj); // ❌ { }
_.cloneDeep(obj); // ✅ { [sym]: 'value' }
三、性能对比
性能数据存在一定争议,取决于测试场景和对象规模:
- 多份来源认为 structuredClone 在大型 / 复杂对象上更快 ,因为底层是原生 C++ 实现,且与 postMessage、IndexedDB 共用同一套高效算法 。
- 也有 benchmark 显示在特定数组拷贝场景下 cloneDeep 略快 (680K vs 590K ops/sec),但这可能受测试用例和运行环境影响。
务实结论 :性能不应作为首要选型依据。两者在大多数业务场景下都够快,差距不会成为瓶颈。功能匹配度才是决定因素。
四、选型决策树
text
javascript
1. 对象包含函数、方法,或类实例的原型链必须保留?
→ 是:使用 lodash.cloneDeep
→ 否:继续
2. 对象包含 Date、Map、Set、RegExp、TypedArray、ArrayBuffer 或循环引用?
→ 是:使用 structuredClone(原生、无依赖)
→ 否:继续
3. 所有值都是严格 JSON 安全的(字符串、有限数字、布尔、null、纯对象/数组)?
→ 是:两者均可,优先 structuredClone 作为默认
五、一句话总结
structuredClone 是现代 JavaScript 深拷贝的默认选择;只有当函数、Symbol 或类实例原型链成为硬性需求时,才需要退回 lodash.cloneDeep 。
如果你的项目是纯数据流(无函数、无类实例),structuredClone 是零依赖、更简洁、且与浏览器底层机制一致的首选。如果数据结构中嵌入了行为(方法、回调),cloneDeep 仍是当前最稳妥的兜底方案。