如何在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
,我们将把clone
的length
设置为原始对象的长度,并使用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 起)中都得到了支持。