
使用扩展运算符 `[...arr]` 只能实现数组的浅拷贝,无法隔离嵌套对象的修改;若需独立修改副本中的对象属性而不影响原数组,必须进行深拷贝或结构化映射。
在 JavaScript 中,数组拷贝常被误认为“复制了全部内容”,但实际行为取决于拷贝深度。你遇到的问题非常典型:
var users = [
{ userUID: '123', userName: 'TOTO' },
{ userUID: '345', userName: 'TITI' },
{ userUID: '678', userName: 'TATA' }
];
var list = [...users]; // ❌ 浅拷贝:新数组引用旧对象
list.map(x => {
if (x.userUID === '678') x.userName = ''; // ✅ 修改的是原对象!
});
console.log(users[2].userName); // → ''(已被意外修改)原因在于:[...users] 仅复制了数组第一层的引用,list[0]、users[0] 指向内存中同一个对象。因此对 x.userName 的赋值操作,本质上是在修改原始对象。
✅ 正确解法一:结构化映射(推荐,轻量且可读性强)
使用 map() 配合对象展开语法,为每个元素创建全新对象:
const list = users.map(obj => ({ ...obj })); // ✅ 每个对象都是新实例
const listModified = list.map(x =>
x.userUID === '678' ? { ...x, userName: '' } : x
);
console.log(users[2].userName); // → 'TATA'(未受影响)
console.log(listModified[2].userName); // → ''✅ 优点:不依赖 JSON 序列化,支持函数、undefined、Date、RegExp 等非序列化值(只要对象本身可展开);性能优于 JSON.parse(JSON.stringify())。
⚠️ 注意:仅适用于对象层级较浅(如本例中单层嵌套)的场景;若对象内含嵌套对象,需递归展开或改用深克隆方案。
✅ 正确解法二:JSON 序列化(简单但有局限)
const list = JSON.parse(JSON.stringify(users)); // ✅ 深拷贝(仅限纯数据)
const listModified = list.map(x =>
x.userUID === '678' ? { ...x, userName: '' } : x
);⚠️ 局限性:会丢失 function、undefined、Symbol、Date(转为字符串)、RegExp、Map/Set 等类型;遇到循环引用直接报错。
立即学习“Java免费学习笔记(深入)”;
✅ 进阶建议:使用现代工具库(生产环境推荐)
对于复杂嵌套结构或需健壮性保障的项目,推荐使用成熟方案:
- Lodash: _.cloneDeep(users)
-
structuredClone()(ES2022+,Chrome 98+/Node.js 17.0+):
const list = structuredClone(users); // ✅ 原生深克隆,支持 Map/Set/Date/RegExp 等
总结
| 方法 | 是否深拷贝 | 兼容性 | 适用场景 |
|---|---|---|---|
| [...arr] / arr.slice() | ❌ 浅拷贝 | ✅ 全平台 | 仅含原始值(string/number/boolean)的数组 |
| arr.map(obj => ({...obj})) | ✅ 单层深拷贝 | ✅ 全平台 | 对象数组,无深层嵌套 |
| JSON.parse(JSON.stringify()) | ✅ 深拷贝(数据限定) | ✅ 全平台 | 快速原型,纯 JSON 数据 |
| structuredClone() | ✅ 完整深拷贝 | ⚠️ 较新环境 | 现代浏览器/Node.js,需高保真克隆 |
| Lodash cloneDeep | ✅ 完整深拷贝 | ✅ 全平台 | 生产级应用,兼容性兜底 |
牢记:“拷贝数组” ≠ “拷贝数组里的对象”——真正安全的副本,必须确保每一层引用都脱离原始内存地址。










