本文详解 javascript 中因引用类型特性导致的数组“反向复制”假象,揭示 obj.var1 = obj.var2 实际是共享引用,并提供可靠的浅拷贝(如展开运算符)与深拷贝实践方案。
本文详解 javascript 中因引用类型特性导致的数组“反向复制”假象,揭示 obj.var1 = obj.var2 实际是共享引用,并提供可靠的浅拷贝(如展开运算符)与深拷贝实践方案。
在 JavaScript 中,数组和对象属于引用类型(reference types),这意味着变量存储的并非数据本身,而是指向内存中实际数据的地址。当你执行 obj.var1 = obj.var2 时,JavaScript 并未创建新数组,而是让 var1 和 var2 共同指向同一个数组实例。后续对任一变量所指数组的修改(例如 obj.var1[1] = 99),都会反映在另一个变量上——这并非“反向复制”,而是引用共享引发的预期外副作用。
以下代码清晰复现该现象:
const obj = {
var1: [],
var2: []
};
obj.var1 = [1, 10, 1];
obj.var2 = obj.var1; // ❌ 浅赋值:var2 指向 var1 所指同一数组
obj.var1 = [0, 2, 0]; // 新数组,var1 指向新地址;var2 仍指向原数组
obj.var1 = obj.var2; // ❌ 再次赋值:var1 重新指向原数组 → 此时 var1 和 var2 共享同一引用
console.log("Step 1:", obj.var1, obj.var2); // [1, 10, 1], [1, 10, 1] ✅
obj.var1[1] = 99; // 修改共享数组的第 1 项
console.log("Step 2:", obj.var1, obj.var2); // [1, 99, 1], [1, 99, 1] ❌如输出所示,var2 的值“被意外改变”,本质是二者始终操作同一底层数组。
✅ 正确做法:使用浅拷贝实现独立副本
若数组仅含基本类型(数字、字符串、布尔值等),推荐使用浅拷贝即可隔离变更:
立即学习“Java免费学习笔记(深入)”;
-
展开运算符(推荐):
obj.var1 = [...obj.var2]; // ✅ 创建新数组,内容相同但内存独立
-
其他可靠浅拷贝方式:
obj.var1 = Array.from(obj.var2); // ✅ obj.var1 = obj.var2.slice(); // ✅(兼容旧环境) obj.var1 = [...obj.var2]; // ✅(ES6+,语义最清晰)
⚠️ 注意:obj.var1 = obj.var2.concat() 或 obj.var2.map(x => x) 同样有效,但展开运算符可读性与性能更优。
⚠️ 进阶提醒:何时需要深拷贝?
若数组元素本身为对象或嵌套数组(例如 [1, {name: 'a'}, [2,3]]),上述浅拷贝无法递归复制内部引用,此时需深拷贝:
// 简单场景(无函数/undefined/Symbol/循环引用)可用 JSON 方法(不推荐生产环境)
obj.var1 = JSON.parse(JSON.stringify(obj.var2));
// 推荐:使用结构化克隆(现代浏览器 & Node.js 17+)
obj.var1 = structuredClone(obj.var2);
// 或引入成熟库如 Lodash
// import { cloneDeep } from 'lodash';
// obj.var1 = cloneDeep(obj.var2);总结
- = 对数组/对象赋值 = 复制引用,非复制数据;
- “备份失效”本质是引用共享,而非语言 Bug;
- 基础类型数组 → 用 [...arr] 实现安全浅拷贝;
- 复杂嵌套结构 → 根据环境选择 structuredClone 或可靠深拷贝方案;
- 始终避免直接赋值引用类型作为状态备份,这是前端状态管理中最易忽视却高频出错的陷阱之一。









