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 }
你可以看到,对象obj是通过引用传递给objCopy的。修改其中一个变量会影响另一个变量,因为它们都引用同一个对象。那我们如何解决这个问题呢?答案是克隆对象。
浅复制
使用展开运算符(...)或 Object.assign(),我们可以克隆对象并根据其属性创建一个新对象。
const shallowClone = obj => Object.assign({}, obj);
let obj = { a: 1, b: 2};
let clone = shallowClone(obj);
let otherClone = shallowClone(obj);
clone.b = 4;
otherClone.b = 6;
// obj = { a: 1, b: 2}
// clone = { a: 1, b: 4 }
// otherClone = { a: 1, b: 6 }
这种技术被称为浅复制,因为它适用于外部(浅层)对象,但如果我们有嵌套(深层)对象,则会失败,因为这些对象最终会通过引用传递。这就引出了下一部分。
图片
深复制
为了创建一个对象的深复制,我们需要递归地克隆每一个嵌套对象,复制嵌套对象和数组。
网上有一些解决方案使用 JSON.stringify() 和 JSON.parse()。虽然这种方法在某些情况下可能有效,但它包含许多问题和性能问题,因此我建议不要使用它。
从边界情况开始,我们需要检查传递的对象是否为空,如果是,则返回 null。否则,我们可以使用 Object.assign() 和一个空对象 ({}) 来创建原始对象的浅复制。
然后,我们将使用 Object.keys() 和 Array.prototype.forEach() 来确定需要深复制哪些键值对。如果对象是一个数组,我们将设置克隆对象的长度与原对象相等,并使用 Array.from() 创建一个克隆对象。否则,我们将递归地调用函数,把当前值作为参数传入。
深复制专用于简单对象和数组。这意味着它无法处理类实例,函数和其他特殊情况。那么,我们如何处理这些情况呢?JavaScript最近给我们提供了一个新的工具来解决这个问题!
使用 structuredClone() 进行深复制
克隆显然是一个很常见也很重要的问题。事实上,JavaScript引入了structuredClone()全局函数,可以用来深度克隆对象。而我们不需要实现复杂的递归函数,只需使用这个函数就可以克隆对象。
这种技术可用于数组和对象,代码最少,是JavaScript中克隆对象的推荐方式,因为它性能最佳且最可靠。