JavaScript 对象克隆:如何避免破坏代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么简单的赋值是一个陷阱?

\n

想象一下,你正在编写一个笔记应用。用户创建了一张带有标题和文本的卡片,然后想复制它,稍作修改。你写下:

\n
const original = { title: '我的计划', text: '买牛奶' };\nconst copy = original;\ncopy.title = '新计划';\nconsole.log(original.title); // '新计划' — 哎呀!
\n

熟悉的情况吗?如果是,那么你遇到了 JavaScript 对象 的主要特性:它们是通过引用传递的。赋值 copy = original 并没有创建一个新对象,而只是复制了指向同一内存区域的引用。对 copy 的任何更改都会反映在 original 上。

\n

在 Stack Overflow 上,问题 "How do I correctly clone a JavaScript object?" 获得了数千次浏览和数十个答案。这是新手甚至经验丰富的开发者最常见的问题之一。在本文中,我们将介绍所有 JavaScript 对象克隆 的方法,从简单到高级,并找出每种情况下哪种方法最好。

\n\n

浅拷贝:快速且简单

\n

浅拷贝会创建一个新对象,但对于嵌套对象(数组、其他对象),它只复制引用。这对于扁平结构来说很理想,但对于深层结构来说很危险。

\n\n

方法 1:展开运算符 (…)

\n

最现代且可读性最强的方法:

\n
const original = { name: '安娜', age: 25 };\nconst clone = { ...original };\nclone.age = 30;\nconsole.log(original.age); // 25 — 一切正常!
\n

优点:写法简短,适用于数组和对象。缺点:不复制方法(函数)和 Symbol 属性。

\n\n

方法 2:Object.assign()

\n

用于在顶层 无引用复制对象 的经典方法:

\n
const source = { a: 1, b: { c: 2 } };\nconst target = Object.assign({}, source);\ntarget.a = 100; // source.a 不会改变\ntarget.b.c = 999; // source.b.c 也会改变 — 浅拷贝!
\n

当需要合并多个对象时,这个方法特别有用:Object.assign({}, obj1, obj2)。

\n\n

方法 3:for…in 循环

\n

一种古老但仍然有效的方法。有助于理解其机制:

\n
function shallowClone(obj) {\n  const clone = {};\n  for (let key in obj) {\n    if (obj.hasOwnProperty(key)) {\n      clone[key] = obj[key];\n    }\n  }\n  return clone;\n}
\n

如今很少使用,但它很好地展示了 手动对象克隆 的工作原理。

\n\n

深拷贝:当需要全部复制时

\n

如果你的对象包含嵌套对象、数组或日期,浅拷贝会让你失望。你需要 js 深拷贝。

\n\n

方法 4:JSON.parse + JSON.stringify

\n

最流行的“权宜之计”:

\n
const original = { \n  name: '彼得', \n  address: { city: '莫斯科', street: '特维尔大街' },\n  date: new Date()\n};\nconst clone = JSON.parse(JSON.stringify(original));\nclone.address.city = '圣彼得堡';\nconsole.log(original.address.city); // '莫斯科' — 太好了!
\n

但有一些注意事项:此方法会丢失函数、undefined、Symbol,并且 Date 会变成字符串。对于简单数据没问题,对于复杂数据则不行。

\n\n

方法 5:structuredClone() — 新标准

\n

自 2022 年起,JavaScript 内置了 structuredClone 方法:

\n
const original = { \n  scores: [10, 20, 30], \n  meta: { level: 'easy' },\n  date: new Date()\n};\nconst clone = structuredClone(original);\nclone.meta.level = 'hard';\nconsole.log(original.meta.level); // 'easy'
\n

优点:支持 Date、Map、Set、ArrayBuffer、循环引用。缺点:不支持函数、DOM 元素、Error。有效

博客

书籍推荐