使用Spread运算符的对象复制实际上是浅还是深?

ric*_*ngh 10 javascript json

我理解传播运算符制作对象的浅拷贝,即克隆对象引用与原始对象相同的引用。但实际行为似乎矛盾和混乱。

const oldObj = {a: {b: 10}};

const newObj = {...oldObj};

oldObj.a.b = 2;
newObj  //{a: {b: 2}}
oldObj  //{a: {b: 2}}
Run Code Online (Sandbox Code Playgroud)

上述行为是有道理的,newObj 也通过更新 oldObj 来更新,因为它们引用相同的位置。

const oldWeirdObj = {a:5,b:3};

const newWeirdObj = {...oldWeirdObj};

oldWeirdObj.a=2;
oldWeirdObj      //{a:2,b:3}
newWeirdObj   //{a:5,b:3}
Run Code Online (Sandbox Code Playgroud)

我不明白,为什么 newWeirdObj 没有像 oldWeirdObj 那样更新?如果我没有错,他们仍然指的是同一个位置,但为什么更新到 oldWeirdObj 而不更新 newWeirdObj ?

Saj*_*med 16

所以,对于这个问题,你要明白什么是shallowcopy和deepcopy。

浅拷贝是对象的按位拷贝,它通过复制原始对象的内存地址来创建新对象。也就是说,它创建一个新对象,其内存地址与原始对象相同。

深拷贝,使用动态分配的内存复制所有字段。也就是说,复制对象的每个值都获得了一个新的内存地址,而不是原始对象。

现在,点差运算符是做什么的?如果数据没有嵌套,它会深度复制数据。对于嵌套数据,深度拷贝嵌套数据的最顶层数据和浅拷贝。

在你的例子中,

const oldObj = {a: {b: 10}};
const newObj = {...oldObj};
Run Code Online (Sandbox Code Playgroud)

它深拷贝顶层数据,即它给属性a,一个新的内存地址,但它浅拷贝嵌套对象,即{b: 10}它现在仍然引用原始oldObj的内存位置。

如果你不相信我检查这个例子,

const oldObj = {a: {b: 10}};
const newObj = {...oldObj};
Run Code Online (Sandbox Code Playgroud)
const oldObj = {a: {b: 10}, c: 2};
const newObj = {...oldObj};

oldObj.a.b = 2; // It also changes the newObj `b` value as `newObj` and `oldObj`'s `b` property allocates the same memory address.
oldObj.c = 5; // It changes the oldObj `c` but untouched at the newObj



console.log('oldObj:', oldObj);
console.log('newObj:', newObj);
Run Code Online (Sandbox Code Playgroud)

你看到的c财产newObj是原封不动的。

如何深度复制对象。

我认为有几种方法。一种常见且流行的方法是使用JSON.stringify()JSON.parse()

.as-console-wrapper {min-height: 100%!important; top: 0;}
Run Code Online (Sandbox Code Playgroud)
const oldObj = {a: {b: 10}, c: 2};
const newObj = JSON.parse(JSON.stringify(oldObj));

oldObj.a.b = 3;
oldObj.c = 4;

console.log('oldObj', oldObj);
console.log('newObj', newObj);
Run Code Online (Sandbox Code Playgroud)

现在,newObj具有全新的内存地址,任何更改oldObj都不会影响newObj.

另一种方法是将oldObj属性一个一个地分配到newObj的新分配的属性中。

.as-console-wrapper {min-height: 100%!important; top: 0;}
Run Code Online (Sandbox Code Playgroud)
const oldObj = {a: {b: 10}, c: 2};
const newObj = {a: {b: oldObj.a.b}, c: oldObj.c};

oldObj.a.b = 3;
oldObj.c = 4;

console.log('oldObj:', oldObj);
console.log('newObj:', newObj);
Run Code Online (Sandbox Code Playgroud)

有一些库可用于深拷贝。你也可以使用它们。

  • 通过传播复制*是*浅复制。const objnew = objold 根本不是副本,只是对同一对象的新引用。根据定义,复制对象键和值但不进行更深层次的复制是浅复制。第二种深度复制方法不可扩展且非常不实用。 (5认同)
  • @Sajeeb Ahamed 你解释得很好。但我不认为这种方法“JSON.parse(JSON.stringify(oldObj))”有什么好处。由于它有各种缺点,如果不考虑它们,使用时可能会很危险。它将删除您的 getter、setter、类原型、带有“undefined”、“Symbol”的键、函数值。将 Date 对象转换为字符串,将“NaN”转换为“null”。将您的“Set”、“Map”转换为常规对象等。说到性能,这表现最差。/sf/ask/3394604531/ (5认同)
  • 在第一个示例中 *const oldObj = {a: {b: 10}}* 如果这样复制 *const newObj = {...oldObj}* 这实际上意味着属性 *a* 现在存储在新的内存地址;然而,该内存地址中保存的值是旧对象的地址,即{b: 10}。如果它被深度复制,*a* 内的值将是指向新的 {b: 10} 的新内存地址。如果我错了,请纠正我。 (2认同)
  • 我注意到互联网上到处都有这样的说法“如果不嵌套,它就会深度复制数据”,但这是无稽之谈。Spread 运算符执行简单的**浅复制**,句号。问题是人们可能不理解原始值和类 C 语言(如 C++、C#、Java 和 JavaScript)中对象的引用之间的区别。如果数据不是“嵌套的”(意味着只有原始值),它仍然是浅拷贝。如果数据是“嵌套的”(意味着某些项目是对象),它仍然是浅副本。 (2认同)

Phi*_*hil 5

复制数组时,扩展语法有效地深入一层。因此,它可能不适合复制多维数组,如以下示例所示。(Object.assign()也是如此)

let x = [1,2,3];
let y = [...x];

y.shift(); // 1
console.log(x); // 1, 2, 3
console.log(y); // 2, 3

let a = [[1], [2], [3]];
let b = [...a];

b.shift().shift(); // 1

//  Oh no!  Now array 'a' is affected as well:
a
//  [[], [2], [3]]
Run Code Online (Sandbox Code Playgroud)

原始文档