JavaScript 浅拷贝与深拷贝:示例和最佳实践

JavaScript 中使用对象和数组时,创建数据结构的副本是一项常见任务。然而,开发人员在决定浅拷贝和深拷贝时经常面临挑战。误解这些差异可能会导致代码中出现意想不到的副作用。让我们深入研究这些概念、它们的区别以及何时使用它们。

什么是浅拷贝?

浅拷贝会创建一个新对象,其中包含原始对象的顶级属性的副本。对于原始属性(例如数字、字符串、布尔值),会复制值本身。但是,对于对象属性(如数组或嵌套对象),只会复制引用,而不会复制实际数据。

这意味着虽然新对象有自己的顶级属性副本,但嵌套对象或数组在原始对象和副本之间仍然共享。

浅拷贝示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const original = {
name: "Alice",
details: {
age: 25,
city: "Wonderland",
},
};

// Shallow copy
const shallowCopy = { ...original };

// Modify the nested object in the shallow copy
shallowCopy.details.city = "Looking Glass";

// Original object is also affected
console.log(original.details.city); // Output: "Looking Glass"

如何使用扩展运算符

(...) 创建浅拷贝:

1
const shallowCopy = { ...originalObject };

使用 Object.assign()

1
const shallowCopy = Object.assign({}, originalObject);

虽然这些方法快速且简单,但它们不适用于深度嵌套的对象。

什么是深层复制?

深层复制会复制原始对象的每个属性和子属性。这可确保副本完全独立于原始对象,并且对副本的更改不会影响原始对象。

处理嵌套对象或数组等复杂数据结构时,深层复制必不可少,尤其是在数据完整性至关重要的情况下。

深层复制示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const original = {
name: "Alice",
details: {
age: 25,
city: "Wonderland",
},
};

// Deep copy using JSON methods
const deepCopy = JSON.parse(JSON.stringify(original));

// Modify the nested object in the deep copy
deepCopy.details.city = "Looking Glass";

// Original object remains unchanged
console.log(original.details.city); // Output: "Wonderland"

如何创建深层复制

使用 JSON.stringify()JSON.parse()

将对象转换为 JSON 字符串,然后将其解析回新对象。

限制:

无法处理循环引用。

忽略函数、未定义或符号等属性。

1
const deepCopy = JSON.parse(JSON.stringify(originalObject));

使用库:

Lodash 这样的库提供了强大的深度克隆方法。

1
2
const _ = require("lodash");
const deepCopy = _.cloneDeep(originalObject);

自定义递归函数:

为了获得完全控制权,你可以编写递归函数来克隆嵌套对象。

浅拷贝和深拷贝的比较

特性 浅拷贝 深拷贝
范围 仅复制顶层属性。 复制所有层级,包括嵌套数据。
引用 嵌套引用是共享的。 嵌套引用是独立的。
性能 更快更轻量。 由于递归操作而更慢。
使用场景 扁平或最小嵌套对象。 深层嵌套对象或不可变结构。

何时使用浅拷贝

  • 平面对象:处理没有嵌套属性的简单对象时。
  • 性能:当速度至关重要,并且你不需要处理深层嵌套的数据时。
  • 临时更改:当你打算修改顶级属性但共享嵌套数据时。

示例用例

复制用户的设置对象以进行快速调整:

1
2
const userSettings = { theme: "dark", layout: "grid" };
const updatedSettings = { ...userSettings, layout: "list" };

何时使用深度复制

  • 复杂结构:适用于具有多层嵌套的对象。
  • 避免副作用:当你需要确保副本中的更改不会影响原始内容时。
  • 状态管理:在 ReactRedux 等框架中,不变性至关重要。

示例用例

复制游戏或应用程序的状态:

1
2
3
4
5
6
7
8
9
10
const gameState = {
level: 5,
inventory: {
weapons: ["sword", "shield"],
potions: 3,
},
};

// Deep copy ensures no side effects
const savedState = JSON.parse(JSON.stringify(gameState));

常见错误和陷阱

  1. 假设浅拷贝总是足够的:

开发人员经常错误地对嵌套对象使用浅拷贝方法,导致原始数据发生意外更改。

  1. 过度使用 JSON 方法:

虽然 JSON.stringify/JSON.parse 很简单,但它并不适用于所有对象(例如,包含方法或循环引用的对象)。

  1. 忽视性能:

深拷贝方法可能会更慢,尤其是对于大型对象,因此请谨慎使用它们。

总结

了解浅拷贝和深拷贝之间的区别对于编写无错误的 JavaScript 代码至关重要。浅拷贝对于平面结构很有效,而深拷贝对于复杂的嵌套对象则必不可少。根据你的数据结构和应用程序需求选择适当的方法,并通过了解每种方法的局限性来避免潜在的陷阱。