深拷贝

对象的**深拷贝**是指其属性不与从中创建该拷贝的源对象的属性共享相同引用(指向相同的底层值)的拷贝。因此,当您更改源或拷贝时,您可以确信您不会导致另一个对象也发生更改。此行为与浅拷贝的行为形成对比,在浅拷贝中,对源或拷贝中嵌套属性的更改可能会导致另一个对象也发生更改。

如果两个对象o1o2的观察行为相同,则它们在结构上等效。这些行为包括

  1. o1o2的属性具有相同名称,并且顺序相同。
  2. 其属性的值在结构上等效。
  3. 它们的原型链在结构上等效(尽管当我们处理结构等效性时,这些对象通常是普通对象,这意味着它们都继承自Object.prototype)。

结构上等效的对象可以是同一个对象(o1 === o2)或拷贝o1 !== o2)。由于等效的原始值始终比较相等,因此您无法创建它们的拷贝。

我们现在可以更正式地定义深拷贝为

  1. 它们不是同一个对象(o1 !== o2)。
  2. o1o2的属性具有相同名称,并且顺序相同。
  3. 其属性的值彼此是深拷贝。
  4. 它们的原型链在结构上等效。

深拷贝的原型链可能被复制也可能不被复制(通常它们不被复制)。但是,具有结构上不等效的原型链的两个对象(例如,一个是数组,另一个是普通对象)永远不会彼此的拷贝。

如果一个对象的属性都具有原始值,则该对象的拷贝既符合深拷贝的定义,也符合浅拷贝的定义。不过,讨论这种拷贝的深度有点没有意义,因为它没有嵌套属性,我们通常在嵌套属性发生变异的上下文中讨论深拷贝。

在 JavaScript 中,标准的内置对象拷贝操作(扩展语法Array.prototype.concat()Array.prototype.slice()Array.from()Object.assign())不会创建深拷贝(而是创建浅拷贝)。

如果 JavaScript 对象可以序列化,则创建其深拷贝的一种方法是使用JSON.stringify()将其转换为 JSON 字符串,然后使用JSON.parse()将该字符串转换回(全新的)JavaScript 对象

js
const ingredientsList = ["noodles", { list: ["eggs", "flour", "water"] }];
const ingredientsListDeepCopy = JSON.parse(JSON.stringify(ingredientsList));

由于深拷贝与源对象不共享任何引用,因此对深拷贝所做的任何更改都不会影响源对象。

js
// Change the value of the 'list' property in ingredientsListDeepCopy.
ingredientsListDeepCopy[1].list = ["rice flour", "water"];
// The 'list' property does not change in ingredients_list.
console.log(ingredientsList[1].list);
// Array(3) [ "eggs", "flour", "water" ]

但是,虽然上面代码中的对象足够简单,可以序列化,但许多 JavaScript 对象根本无法序列化——例如,函数(带闭包)、符号、表示HTML DOM API中 HTML 元素的对象、递归数据以及许多其他情况。调用JSON.stringify()来序列化这些情况下的对象将失败。因此,无法创建此类对象的深拷贝。

Web API structuredClone()也会创建深拷贝,并且具有允许将源中的可传输对象传输到新拷贝,而不仅仅是克隆的优点。它还处理更多数据类型,例如Error。但请注意,structuredClone()本身并不是 JavaScript 语言的功能——而是实现 Web API 的浏览器和其他 JavaScript 主机的一项功能。并且,调用structuredClone()来克隆不可序列化的对象将与调用JSON.stringify()来序列化它一样失败。

另请参阅