Skip to content

如何在JavaScript中克隆一个对象?

JavaScript的基本数据类型是不可变的,这意味着它们一旦创建就不能改变。然而,对象和数组是可变的,允许在创建后修改它们的值。

在实践中,这意味着原始值是按值传递的,而对象和数组是按引用传递的。考虑以下示例:

let str = 'Hello';
let copy = str;
copy = 'Hi';
// str = 'Hello', copy = 'Hi'

let obj = { a: 1, b: 2 };
let objCopy = obj;
objCopy.b = 4;
// obj = { a: 1, b: 4}, objCopy = { a: 1, b: 4 }

如您所见,对象被按引用传递给objCopy。更改其中一个变量将影响另一个变量,因为它们都引用同一个对象。那么我们该如何解决这个问题呢?克隆对象就是答案。

浅克隆

使用扩展运算符(...)或Object.assign(),我们可以克隆对象并从其属性创建一个新对象。

const shallowClone = obj => Object.assign({}, obj);

let obj = { a: 1, b: 2};
let clone = shallowClone(obj);
let otherClone = shallowClone(obj);

```js
clone.b = 4;
otherClone.b = 6;
// obj = { a: 1, b: 2}
// clone = { a: 1, b: 4 }
// otherClone = { a: 1, b: 6 }

这种技术被称为浅克隆,因为它适用于外部(浅层)对象,但如果我们有嵌套的(深层)对象,它将无法正常工作,因为这些对象最终将通过引用传递。这就引出了下一节。

深克隆

为了创建一个深克隆的对象,我们需要递归地克隆每个嵌套的对象,同时克隆嵌套的对象和数组。

[!IMPORTANT]

一些网络上的解决方案使用JSON.stringify()JSON.parse()。虽然这种方法在某些情况下可能有效,但它存在许多问题和性能问题,所以我建议不要使用它。

首先,我们需要检查传入的对象是否为null,如果是,则返回null。否则,我们可以使用Object.assign()和一个空对象({})来创建原始对象的浅克隆。

接下来,我们将使用Object.keys()Array.prototype.forEach()来确定哪些键值对需要进行深克隆。如果对象是一个Array,我们将把clonelength设置为原始对象的长度,并使用Array.from()来创建一个克隆。否则,我们将使用当前值作为参数递归调用函数。

const deepClone = obj => {
  if (obj === null) return null;
  let clone = Object.assign({}, obj);
  Object.keys(clone).forEach(
    key =>
      (clone[key] =
        typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
  );
  if (Array.isArray(obj)) {
    clone.length = obj.length;
    return Array.from(clone);
  }
  return clone;
};

const a = { foo: 'bar', obj: { a: 1, b: 2 } };
const b = deepClone(a); // a !== b, a.obj !== b.obj

这段代码片段专门针对普通对象和数组设计。这意味着它无法处理类实例函数和其他特殊情况。那么,我们如何处理这些情况呢?JavaScript最近为我们提供了一个解决这个问题的新工具!

使用 structuredClone() 进行深拷贝

显然,拷贝是一个相当常见且重要的问题。为此,JavaScript 引入了 structuredClone() 全局函数,可以用于深拷贝对象。我们可以简单地使用这个函数来克隆对象,而不是实现一个复杂的递归函数。

const a = { x: 1, y: { y1: 'a' }, z: new Set([1, 2]) };
const b = structuredClone(a); // a !== b, a.y !== b.y, a.z !== b.z

这种技术可以用于数组和对象,代码量很少,是 JavaScript 中克隆对象的推荐方式,因为它是最高效和可靠的方式。

[!NOTE]

structuredClone() 函数是语言中相对较新的添加。即便如此,它在所有现代浏览器和 Node.js(自版本 v17.0.0 起)中都得到了支持。