🎯 问题场景:深拷贝,你还在用 JSON.parse + JSON.stringify 吗?
js
const user = {
name: '张三',
age: 30,
birthday: new Date('2000-01-01'),
roles: new Set(['admin', 'editor']),
settings: new Map([['theme', 'dark']]),
metadata: undefined,
greet: () => console.log('hi'),
};
// ❌ 经典做法:JSON 序列化
const copy = JSON.parse(JSON.stringify(user));
console.log(copy.birthday) // "2000-01-01T00:00:00.000Z" ← 变字符串了!
console.log(copy.roles) // {} ← Set 丢了!
console.log(copy.settings) // {} ← Map 丢了!
console.log(copy.metadata) // 键都没了
console.log(copy.greet) // greet 直接消失
你是不是也经常被 JSON.parse(JSON.stringify(obj)) 坑过?Date 变字符串、Set/Map/undefined 静默丢失、循环引用直接报错------这套"祖传深拷贝"痛点太多了。
从 2022 年起,浏览器和 Node.js 已全面支持原生 structuredClone() API,上面这些问题基本都能解决。
🔍 原因分析:JSON 深拷贝为什么脆弱?
JSON.parse(JSON.stringify(obj)) 的本质是把对象序列化为 JSON 字符串再反序列化,而 JSON 只支持 6 种数据类型:
| 数据类型 | JSON 支持 | 拷贝后的结果 |
|---|---|---|
| string / number / boolean / null | ✅ | 正常 |
| 普通对象 / 数组 | ✅ | 正常 |
| Date | ❌ | 变字符串 |
| Map / Set / RegExp / Error | ❌ | 变空对象 |
| undefined / Function / Symbol | ❌ | 键被静默丢弃 |
| BigInt | ❌ | 报错 |
| 循环引用 | ❌ | 报错 |
这意味着------只要数据里有一个 Date、Map、undefined 字段,JSON 深拷贝就是不可靠的。
💡 解决方案:structuredClone 实战
1️⃣ 基础用法
js
const original = {
name: '张三',
birthday: new Date('2000-01-01'),
roles: new Set(['admin']),
settings: new Map([['theme', 'dark']]),
tags: ['前端', 'JavaScript'],
nested: { a: 1 },
};
// ✅ 一行搞定
const cloned = structuredClone(original);
console.log(cloned.birthday instanceof Date) // true ✅
console.log(cloned.roles instanceof Set) // true ✅
console.log(cloned.settings instanceof Map) // true ✅
console.log(cloned === original) // false(深拷贝)
console.log(cloned.tags === original.tags) // false(嵌套也深拷贝)
2️⃣ 处理循环引用(JSON 直接挂)
js
const obj = { name: '循环引用' };
obj.self = obj; // 👈 自己引用自己
// ❌ JSON 法
JSON.parse(JSON.stringify(obj)); // TypeError: Converting circular structure to JSON
// ✅ structuredClone
const cloned = structuredClone(obj); // ✅ 完美处理
console.log(cloned.self === cloned); // true(引用关系保持)
3️⃣ Transferable:零拷贝转移
这是 structuredClone 的隐藏大招 ------对于 ArrayBuffer 等可转移对象,可以零拷贝把数据所有权转移给新对象:
js
const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
const view = new Uint8Array(buffer);
view[0] = 42;
// ⚡ 使用 transfer,原 buffer 被清空
const cloned = structuredClone({ data: buffer }, { transfer: [buffer] });
console.log(buffer.byteLength); // 0 ← 所有权已转移!
console.log(cloned.data.byteLength); // 104857600 ✅
💡 适用场景:Web Worker 传递大文件数据、Canvas 截图数据传递等。100MB 的数据零拷贝复制,性能差异巨大。
4️⃣ 手写一个更健壮的深拷贝(兜底方案)
虽然 structuredClone 很强,但它不支持函数和 Symbol。如果项目中确实需要拷贝这些,可以组合使用:
js
function deepClone(obj, hash = new WeakMap()) {
// 先尝试 structuredClone
try {
return structuredClone(obj);
} catch {
// 结构化克隆不支持的类型,走递归
}
if (obj === null || typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj); // 处理循环引用
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) {
const copy = new Map();
hash.set(obj, copy);
obj.forEach((v, k) => copy.set(deepClone(k, hash), deepClone(v, hash)));
return copy;
}
if (obj instanceof Set) {
const copy = new Set();
hash.set(obj, copy);
obj.forEach(v => copy.add(deepClone(v, hash)));
return copy;
}
const clone = Object.create(Object.getPrototypeOf(obj));
hash.set(obj, clone);
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], hash);
}
return clone;
}
实战建议:优先用
structuredClone,只在需要拷贝函数/Symbol 时才写兜底。现代项目 95% 的场景structuredClone就够了。
⚠️ 注意事项(踩坑预警)
❌ 不支持的类型
js
structuredClone({ fn: () => {} });
// DOMException: function could not be cloned
structuredClone({ sym: Symbol('foo') });
// DOMException: symbol could not be cloned
structuredClone({ el: document.body });
// DOMException: HTMLBodyElement could not be cloned
| 不支持 | 替代方案 |
|---|---|
| Function / 方法 | 不能拷贝,需重新绑定或序列化 |
| Symbol | 不能拷贝 |
| DOM 节点 | 不能拷贝 |
| Error 对象 | 不能拷贝(会抛异常) |
| WeakMap / WeakRef | 不能拷贝 |
✅ 支持的类型一览
structuredClone 实际支持远超 JSON 的 20+ 种类型:
- ✅ 所有原始类型(含 BigInt)
- ✅ 普通对象、数组
- ✅ Date、RegExp、Map、Set、Error
- ✅ ArrayBuffer、TypedArray、DataView
- ✅ Blob、File、ImageData
- ✅ URL、URLSearchParams
- ✅ 大多数内置类实例(class 实例能保持原型链)
😱 一个容易忽略的坑
js
const obj = { count: 0 };
const cloned = structuredClone(obj);
cloned.count = 1;
console.log(obj.count); // 0 ✅ 深拷贝,没问题
// 但如果是 class 实例呢?
class User {
constructor(name) { this.name = name; }
greet() { return `Hello, ${this.name}`; }
}
const user = new User('张三');
const clonedUser = structuredClone(user);
console.log(clonedUser instanceof User); // ✅ true!原型链保留
console.log(clonedUser.greet()); // "Hello, 张三" ✅ 方法也在
注意:只有属性 被拷贝,class 的构造器不会执行 。structuredClone 会在内部维护一个注册表,对已知类型做正确的反序列化。
✅ 要点总结
structuredClone是浏览器/Node.js 17+ 原生提供的深拷贝 API ,语法structuredClone(value, { transfer? })- 相比
JSON.parse(JSON.stringify())优势巨大:支持 Date、Map、Set、RegExp、BigInt、循环引用等 - Transferable 零拷贝:对 ArrayBuffer 等大对象可转移所有权,性能碾压
- 局限性:不支持 Function、Symbol、DOM 节点、Error(v8 限制)、WeakMap
- 实战策略 :95% 场景用
structuredClone就够了;兜底用 WeakMap 递归方案 - 兼容性:Chrome 98+ / Firefox 94+ / Safari 15.4+ / Node 17+,已可放心生产使用
下次面试被问"深拷贝怎么实现",别一上来就说
JSON.parse(JSON.stringify)了------先给structuredClone,再聊兜底方案,这才是专业前端该有的回答 👍