structuredClone vs lodash.cloneDeep:核心差异与选型指南

23 阅读3分钟

做前端这么多年,JSON.parse(JSON.stringify(obj)) 这行代码你一定不陌生。

它简单、好用、随处可见——但也处处是坑:

  • 函数?没了。
  • undefined?没了。
  • Symbol?没了。
  • 循环引用?直接报错。
  • Date、RegExp、Map、Set?统统变成你不认识的样子。

2022 年,浏览器终于推出了原生的深拷贝 API —— structuredClone() 。它一度被称作"JSON 深拷贝的终结者"。

但现实是:它并不是 lodash.cloneDeep 的替代品,而是互补品。

一个处理纯数据,一个兜底带行为的数据。今天就把两者的边界讲清楚。

一、核心能力对比

根据 greatfrontend 链接1 链接2 整理的对比表:

值 / 场景structuredCloneLodash cloneDeep
嵌套对象 / 数组✅ 克隆✅ 克隆
Date✅ 保留 Date✅ 保留 Date
RegExp✅ 保留 RegExp✅ 保留 RegExp
Map / Set✅ 克隆✅ 克隆
循环引用✅ 支持✅ 支持
函数 / 方法❌ 抛 DataCloneError✅ 按引用保留
Symbol 键❌ 不克隆✅ 克隆
类实例原型链❌ 丢失,变普通对象✅ 保留原型链
BigInt✅ 保留✅ 保留
instanceof 检查❌ 类实例克隆后 instanceof 失效✅ 保持类实例身份

二、关键差异详解

1. 函数处理:最本质的分水岭

structuredClone 遇到函数直接抛异常:

js

structuredClone({ fn: () => {} }); 
// ❌ DataCloneError: () => {} could not be cloned.

cloneDeep 将函数按引用保留 :

js

const obj = { fn: () => console.log('hello') };
const clone = _.cloneDeep(obj);
clone.fn === obj.fn; // true(函数本身是同一引用)

这意味着:如果你的数据模型包含方法或回调函数,structuredClone 完全不可用,只能选 cloneDeep。

2. 原型链:类实例的命运不同

structuredClone 返回普通对象,原型链被剥离 :

js

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

cloneDeep 保留原型链 :

js

const cloned = _.cloneDeep(u);
cloned instanceof User; // ✅ true
cloned.greet();         // ✅ 'Hi, 张三'

但注意:两者都不会重新执行构造函数,只在构造时生效的不变量不会在克隆对象上重新应用 。

3. Symbol 键的处理

structuredClone 不克隆 Symbol 键的属性,cloneDeep 则可以 。

js

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),但这可能受测试用例和运行环境影响。

务实结论:性能不应作为首要选型依据。两者在大多数业务场景下都够快,差距不会成为瓶颈。功能匹配度才是决定因素。

四、选型决策树

根据 greatfrontend 的建议,按顺序判断 :

text

1. 对象包含函数、方法,或类实例的原型链必须保留?
   → 是:使用 lodash.cloneDeep
   → 否:继续

2. 对象包含 Date、Map、Set、RegExp、TypedArray、ArrayBuffer 或循环引用?
   → 是:使用 structuredClone(原生、无依赖)
   → 否:继续

3. 所有值都是严格 JSON 安全的(字符串、有限数字、布尔、null、纯对象/数组)?
   → 是:两者均可,优先 structuredClone 作为默认

五、一句话总结

structuredClone 是现代 JavaScript 深拷贝的默认选择;只有当函数、Symbol 或类实例原型链成为硬性需求时,才需要退回 lodash.cloneDeep 。

如果你的项目是纯数据流(无函数、无类实例),structuredClone 是零依赖、更简洁、且与浏览器底层机制一致的首选。如果数据结构中嵌入了行为(方法、回调),cloneDeep 仍是当前最稳妥的兜底方案。