引言:为什么普通拷贝行不通?
\n\n想象一下:您正在编写一个处理复杂用户配置的应用程序。您执行了 const newConfig = oldConfig,修改了几个字段——然后突然发现 原始对象也跟着变了。是不是很熟悉?👋
在 JavaScript 中,对象是 按引用传递 的。这意味着简单的赋值操作并不会创建一个新对象,而只是复制了一个指向旧对象的指针。这就是困扰 Stack Overflow 上成千上万开发者的核心问题:"What is the most efficient way to deep clone an object in JavaScript?"。
\n\n在本文中,我们将深入探讨 所有流行的深度拷贝方法,它们的优缺点以及性能表现。您将了解在 2024 年应该选择哪种方法,并学会编写自己可靠的 deep clone。
\n\n为什么我们需要讨论深度拷贝?
\n\n深度拷贝(deep clone)会创建一个 完全独立的副本,包含所有嵌套结构。这与浅拷贝(shallow copy)不同,浅拷贝中嵌套对象仍然是共享的。
\n\n以下是需要深度拷贝的典型场景:
\n- \n
- 使用 Redux / Zustand: 不能直接修改状态,需要创建副本。 \n
- 数据序列化 以便发送到服务器。 \n
- 处理缓存 或不可变的配置。 \n
- 单元测试: 避免影响原始数据。 \n
但事情没那么简单。如果您尝试拷贝一个包含函数、undefined、Symbol 或循环引用的对象——许多方法会 失败 或丢失数据。让我们逐一详细分析每种方法。
方法 1:JSON.parse(JSON.stringify(obj)) —— 老派但有效
\n\n这是初学者中最流行的方法。它简单且编写快速:
\n\nconst original = {\n name: "安娜",\n age: 25,\n address: { city: "莫斯科", zip: 101000 },\n hobbies: ["阅读", "跑步"]\n};\n\nconst clone = JSON.parse(JSON.stringify(original));\n\nclone.address.city = "圣彼得堡";\nconsole.log(original.address.city); // "莫斯科" — ✅ 副本是独立的!\n\n\n优点:
\n- \n
- 🔥 语法非常简单。 \n
- 🔥 适用于大多数简单情况。 \n
缺点(关键!):
\n- \n
- ❌ 丢失函数 —— 它们会变成
undefined。 \n - ❌ 丢失 undefined —— 值为
undefined的字段会消失。 \n - ❌ 丢失 Symbol —— Symbol 类型的键会丢失。 \n
- ❌ 无法处理循环引用 —— 会抛出
TypeError错误。 \n - ❌ 日期问题 ——
Date会变成字符串。 \n - ❌ Map、Set、RegExp 问题 —— 它们会变成空对象。 \n
结论: 仅适用于保证可序列化为 JSON 的数据(字符串、数字、数组、简单对象)。
\n\n方法 2:Structured Clone API —— 现代标准(2024)
\n\n从 2022 年开始,JavaScript 引入了一种 原生 深度拷贝方法——structuredClone。它在所有现代浏览器和 Node.js 17+ 中可用。
const clone = structuredClone(original);\n\nclone.address.city = "喀山";\nconsole.log(original.address.city); // "莫斯科" — ✅\n\n\nstructuredClone 能做什么:
\n- \n
- ✅ 循环引用 —— 可以处理! \n
- ✅ 日期 —— 保留
Date类型。 \n - ✅ Map 和 Set —— 正确拷贝。 \n
- ✅ RegExp —— 同样有效。 \n
- ✅ ArrayBuffer 和 Ty \n