在前端开发时遇到一个问题,在我使用 扩展运算符 ...Object.assign() 进行对象拷贝时,发现修改新对象的嵌套属性会影响原对象。这让我意识到 JavaScript 中的对象拷贝分为浅拷贝和深拷贝两种方式。

一、浅拷贝(Shallow Copy)

浅拷贝是指只复制对象的第一层属性,如果属性值是引用类型(如对象、数组),则只复制其引用地址。

常见浅拷贝方法

  • Object.assign()
  • 展开运算符 ...
  • Array.prototype.slice()concat()

示例:

1
2
3
4
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 };
obj2.b.c = 100;
console.log(obj1.b.c); // 100,说明 b 只是引用

二、深拷贝(Deep Copy)

深拷贝会递归复制所有层级的属性,两个对象完全独立,互不影响。

常见深拷贝方法

  • JSON.parse(JSON.stringify(obj))(有局限:不能拷贝函数、undefined、Symbol、循环引用等)
  • 第三方库:lodash.cloneDeep

示例:

1
2
3
4
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 100;
console.log(obj1.b.c); // 2,互不影响

三、在 React 中的应用

React 的 state 不可变性要求我们不能直接修改原对象,而应通过拷贝生成新对象后再 setState。

错误示例(直接修改引用):

1
2
3
const [user, setUser] = useState({ name: "Tom", info: { age: 20 } });
user.info.age = 30;
setUser(user); // 可能不会触发视图更新

正确示例(深拷贝):

1
2
3
4
const [user, setUser] = useState({ name: "Tom", info: { age: 20 } });
const newUser = JSON.parse(JSON.stringify(user));
newUser.info.age = 30;
setUser(newUser); // 触发视图更新

推荐使用 immerlodash.cloneDeep 等库简化深拷贝操作。

四、手动实现深拷贝的步骤

手动实现一个通用的深拷贝函数,需考虑如下:

  • 递归遍历对象和数组
  • 处理基本类型、Date、RegExp、Map、Set 等
  • 处理循环引用

简单实现(不处理循环引用/特殊对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj;
if (Array.isArray(obj)) {
return obj.map(deepClone);
}
const result = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}

进阶实现(处理循环引用):

1
2
3
4
5
6
7
8
9
10
11
12
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj;
if (hash.has(obj)) return hash.get(obj);
let result = Array.isArray(obj) ? [] : {};
hash.set(obj, result);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key], hash);
}
}
return result;
}

五、总结

  • 浅拷贝只复制一层,深拷贝递归复制所有层级。
  • React 状态管理推荐深拷贝,避免直接修改引用。
  • 复杂对象使用第三方库或手动实现深拷贝。