structuredClone:原生深拷贝 API 的正确打开方式

🎯 问题场景:深拷贝,你还在用 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 报错
循环引用 报错

这意味着------只要数据里有一个 DateMapundefined 字段,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 会在内部维护一个注册表,对已知类型做正确的反序列化。


✅ 要点总结

  1. structuredClone 是浏览器/Node.js 17+ 原生提供的深拷贝 API ,语法 structuredClone(value, { transfer? })
  2. 相比 JSON.parse(JSON.stringify()) 优势巨大:支持 Date、Map、Set、RegExp、BigInt、循环引用等
  3. Transferable 零拷贝:对 ArrayBuffer 等大对象可转移所有权,性能碾压
  4. 局限性:不支持 Function、Symbol、DOM 节点、Error(v8 限制)、WeakMap
  5. 实战策略 :95% 场景用 structuredClone 就够了;兜底用 WeakMap 递归方案
  6. 兼容性:Chrome 98+ / Firefox 94+ / Safari 15.4+ / Node 17+,已可放心生产使用

下次面试被问"深拷贝怎么实现",别一上来就说 JSON.parse(JSON.stringify) 了------先给 structuredClone,再聊兜底方案,这才是专业前端该有的回答 👍

相关推荐
大猫会长2 小时前
获取favicon.ico的方法
前端·javascript·html
无人生还2 小时前
从 Vue3 到 React · 快速上手系列第 5 篇:事件处理与表单(受控组件 vs v-model)
前端·vue.js·react.js
默_笙3 小时前
🎀 大模型说"呀"还是"吗"?temperature 和 Top K 在偷偷控制它的"性格"
前端·javascript
渣波3 小时前
别再死磕MySQL了!用Milvus+LangChain给AI加个“海马体”,手把手带你写个RAG日记本
前端
乌夷3 小时前
vue知识点
前端·javascript·vue.js
何时梦醒3 小时前
🚀 从零上手 Milvus 向量数据库:打造一个 AI 智能日记本
前端·人工智能
blns_yxl3 小时前
拖拽排序列表(HTML+JS)
前端·javascript·html
Data3 小时前
把 React 前端做成「插件市场」:前后端联动的行业包可插拔架构
前端
光影少年3 小时前
RN Bridge 原理
前端·react native·react.js