引言:为什么简单的赋值是一个陷阱?
\n想象一下,你正在编写一个笔记应用。用户创建了一张带有标题和文本的卡片,然后想复制它,稍作修改。你写下:
\nconst original = { title: '我的计划', text: '买牛奶' };\nconst copy = original;\ncopy.title = '新计划';\nconsole.log(original.title); // '新计划' — 哎呀!\n熟悉的情况吗?如果是,那么你遇到了 JavaScript 对象 的主要特性:它们是通过引用传递的。赋值 copy = original 并没有创建一个新对象,而只是复制了指向同一内存区域的引用。对 copy 的任何更改都会反映在 original 上。
在 Stack Overflow 上,问题 "How do I correctly clone a JavaScript object?" 获得了数千次浏览和数十个答案。这是新手甚至经验丰富的开发者最常见的问题之一。在本文中,我们将介绍所有 JavaScript 对象克隆 的方法,从简单到高级,并找出每种情况下哪种方法最好。
\n\n浅拷贝:快速且简单
\n浅拷贝会创建一个新对象,但对于嵌套对象(数组、其他对象),它只复制引用。这对于扁平结构来说很理想,但对于深层结构来说很危险。
\n\n方法 1:展开运算符 (…)
\n最现代且可读性最强的方法:
\nconst original = { name: '安娜', age: 25 };\nconst clone = { ...original };\nclone.age = 30;\nconsole.log(original.age); // 25 — 一切正常!\n优点:写法简短,适用于数组和对象。缺点:不复制方法(函数)和 Symbol 属性。
\n\n方法 2:Object.assign()
\n用于在顶层 无引用复制对象 的经典方法:
\nconst 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)。
方法 3:for…in 循环
\n一种古老但仍然有效的方法。有助于理解其机制:
\nfunction 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最流行的“权宜之计”:
\nconst 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 会变成字符串。对于简单数据没问题,对于复杂数据则不行。
方法 5:structuredClone() — 新标准
\n自 2022 年起,JavaScript 内置了 structuredClone 方法:
\nconst 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。有效