
使用扩展运算符 `[...arr]` 只能实现数组的浅拷贝,无法隔离嵌套对象的引用;修改副本中的对象属性仍会影响原数组——本文详解原因及多种可靠深拷贝方案。
在 JavaScript 中,数组是引用类型,而数组中存储的对象(如 {userUID: '123', userName: 'TOTO'})同样是引用类型。当你执行:
var users = [{userUID: '123', userName: 'TOTO'}, {userUID: '345', userName: 'TITI'}, {userUID: '678', userName: 'TATA'}];
var list = [...users]; // 浅拷贝:仅复制外层数组,内部对象引用未变list 是一个新数组,但它的每个元素仍指向 users 中相同的对象内存地址。因此后续调用:
var listModified = list.map(x => x.userName = x.userUID === '678' ? '' : x.userName);
实际是在直接修改原始对象的 userName 属性——这正是 console.log(users) 显示被篡改的根本原因。
✅ 正确做法:创建深拷贝(deep copy),确保新数组及其所有嵌套对象均为独立副本。
立即学习“Java免费学习笔记(深入)”;
✅ 推荐方案一:结构化克隆(现代标准,推荐优先使用)
// ✅ 安全、高效、支持多数内置类型(Date, Map, Set, RegExp 等)
const list = structuredClone(users);
const listModified = list.map(obj =>
obj.userUID === '678' ? { ...obj, userName: '' } : obj
);
console.log(users); // 原数组完全不变 ✅⚠️ 注意:structuredClone() 在 Node.js ≥17.0 和现代浏览器(Chrome 98+、Firefox 94+、Safari 16.4+)中已原生支持;旧环境需降级方案。
✅ 推荐方案二:map() + 展开语法(轻量、兼容性好)
// ✅ 创建新对象副本,避免共享引用
const list = users.map(obj => ({ ...obj }));
const listModified = list.map(obj =>
obj.userUID === '678' ? { ...obj, userName: '' } : obj
);此方式对每个对象执行一次浅拷贝({...obj}),适用于纯 POJO(Plain Old JavaScript Objects),不包含函数、undefined、Symbol 或循环引用。
⚠️ 谨慎使用:JSON.parse(JSON.stringify())
const list = JSON.parse(JSON.stringify(users)); // ❗有严重限制
- ✅ 简单有效,兼容所有环境
- ❌ 不支持 undefined、function、Symbol、Date(转为字符串)、RegExp、Map/Set、BigInt,且会丢失原型链和循环引用(直接报错)
→ 仅适用于结构简单、纯数据的 JSON-safe 场景。
? 验证是否真正隔离
可通过 Object.is() 或对比引用验证:
console.log(list[0] === users[0]); // false → 数组元素已不同引用 ✅ console.log(list[0].userName); // ''(修改后) console.log(users[0].userName); // 'TOTO'(原始值未变)✅
✅ 最佳实践总结
| 场景 | 推荐方法 |
|---|---|
| 现代环境(Node.js / 浏览器) | structuredClone() —— 安全、完整、标准 |
| 兼容旧环境 + 纯对象数组 | users.map(obj => ({...obj})) —— 清晰、无副作用 |
| 快速原型或已知 JSON-safe 数据 | JSON.parse(JSON.stringify(arr)) —— 但务必评估数据结构限制 |
切记:“复制数组” ≠ “复制数组里的对象”。真正的数据隔离,始于理解引用本质,成于选择恰当的深拷贝策略。










