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

IT技术 javascript
2021-01-24 04:37:25

...其中每个对象还引用同一数组中的其他对象?

当我第一次想到这个问题时,我只是想到了类似的东西

var clonedNodesArray = nodesArray.clone()

将存在并搜索有关如何在 JavaScript 中克隆对象的信息。我确实在 Stack Overflow 上找到了一个问题(由同一个@JohnResig 回答),他指出使用 jQuery 你可以做到

var clonedNodesArray = jQuery.extend({}, nodesArray);

克隆一个对象。不过我试过了,这只会复制数组中对象的引用。所以如果我

nodesArray[0].value = "red"
clonedNodesArray[0].value = "green"

nodeArray[0] 和 clonedNodesArray[0] 的值都将变为“绿色”。然后我试过了

var clonedNodesArray = jQuery.extend(true, {}, nodesArray);

它深度复制了一个对象,但我分别FirebugOpera Dragonfly得到了“太多递归”和“控制堆栈溢出”消息

你会怎么做?这是不应该做的事情吗?在 JavaScript 中是否有可重用的方法来做到这一点?

6个回答

考虑对象数组中所有可能对象的通用解决方案可能是不可能的。也就是说,如果您的数组包含具有 JSON 可序列化内容的对象(没有函数、noNumber.POSITIVE_INFINITY等),那么以性能为代价避免循环的一种简单方法就是这种纯粹的单行解决方案。

let clonedArray = JSON.parse(JSON.stringify(nodesArray))

总结下面的评论,这种方法的主要优点是它还克隆了数组的内容,而不仅仅是数组本身。主要的缺点是它只能处理 JSON 可序列化的内容,而且它的性能比传播方法慢约 30 倍。

如果数组中有浅层对象,并且 IE6 是可以接受的,更好的方法是将扩展运算符与 .map 数组运算符结合使用。对于两层深的情况(如下面附录中的数组):

clonedArray = nodesArray.map(a => {return {...a}})

原因有两个:1)它要快得多(参见下面的基准比较),并且它还允许数组中的任何有效对象。

*附录:性能量化基于将这个对象数组克隆一百万次:

 [{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic1.jpg?raw=true', id: '1', isFavorite: false}, {url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic2.jpg?raw=true', id: '2', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic3.jpg?raw=true', id: '3', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic4.jpg?raw=true', id: '4', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic5.jpg?raw=true', id: '5', isFavorite: true},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic6.jpg?raw=true', id: '6', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic7.jpg?raw=true', id: '7', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic8.jpg?raw=true', id: '8', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic9.jpg?raw=true', id: '9', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic10.jpg?raw=true', id: '10', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic11.jpg?raw=true', id: '11', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic12.jpg?raw=true', id: '12', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic13.jpg?raw=true', id: '13', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic14.jpg?raw=true', id: '14', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic15.jpg?raw=true', id: '15', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic16.jpg?raw=true', id: '16', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic17.jpg?raw=true', id: '17', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic18.jpg?raw=true', id: '18', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic19.jpg?raw=true', id: '19', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic20.jpg?raw=true', id: '20', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic21.jpg?raw=true', id: '21', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic22.jpg?raw=true', id: '22', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic23.jpg?raw=true', id: '23', isFavorite: false}]

要么使用:

let clonedArray = JSON.parse(JSON.stringify(nodesArray))

或者:

clonedArray = nodesArray.map(a => {return {...a}})

map/spread 方法每次传递需要 0.000466 毫秒,JSON.parse 和 JSON.stringify 每次传递需要 0.014771 毫秒。*

如果数组对象有日期时间,则返回字符串而不是日期时间!新日期 !== JSON.parse(JSON.stringify(new Date))
2021-03-15 04:37:25
这通常是一种糟糕的方法,除非您的数组仅包含基元和/或本身仅包含字符串/数字/布尔基元的对象(即使null并且undefined将是问题,因为 JSON 不支持它们)。此外,与 相比,它的效率要低得多old_array.slice(0);,它应该可以更好更快地工作。
2021-03-23 04:37:25
OP 问题中的关键行,上面的这个答案完全忽略了:...其中每个对象还引用了同一数组中的其他对象?
2021-03-30 04:37:25
这可能适用于 JSON 数据,但如果您的数组包含任何具有方法的函数或对象实例,请告别它们。
2021-04-05 04:37:25
如果您有一个包含值 Infinity 的数组,请小心。该值丢失(之后为空)。( jsfiddle.net/klickagent/ehm4bd3s )
2021-04-09 04:37:25

我用Object.assign解决了对象数组的克隆问题

const newArray = myArray.map(a => Object.assign({}, a));

甚至更短的扩展语法

const newArray = myArray.map(a => ({...a}));
@MatthewJamesDavis 您可以通过替换{}new Dinosaur().
2021-03-10 04:37:25
但是如果 myArray 包含一堆 Dinosaurs,那么 newArray 包含一堆 Objects。这很蹩脚,你不同意吗?
2021-03-20 04:37:25
最好的方法,因为它使对象函数保持活动状态,然后使用 JSON.parse(JSON.stringify(nodesArray)) 丢失它们
2021-03-24 04:37:25
这对一组对象非常有用,如果这些对象只包含原始属性......这就是我需要的,谢谢
2021-03-29 04:37:25
浅拷贝不是深拷贝
2021-04-09 04:37:25

如果您只需要一个浅拷贝,一个非常简单的方法是:

new_array = old_array.slice(0);
对于实际上并未克隆的对象数组,更新到 new_array 也将更新 old_array 。
2021-03-13 04:37:25
实际上这不适用于对象数组。返回的数组 byslice将是一个新数组,但将包含对原始数组对象的引用。
2021-03-20 04:37:25
这仅适用于“泛型”int、字符串等,但不适用于对象数组。
2021-03-22 04:37:25
我认为您不必通过0.slice()无论如何至少可以在 chrome 中调用
2021-03-27 04:37:25
但这实际上行不通,不是吗?我的意思是,这不是如何克隆一组对象的问题的答案。这是克隆简单数组的解决方案。
2021-04-08 04:37:25

浅拷贝的问题是所有对象都没有被克隆。虽然对每个对象的引用在每个数组中都是唯一的,但一旦您最终抓住它,您将像以前一样处理相同的对象。您克隆它的方式没有任何问题……使用 Array.slice() 会出现相同的结果。

您的深层复制出现问题的原因是因为您最终得到了循环对象引用。Deep会尽可能地深入,如果你有一个圆圈,它会无限地继续下去,直到浏览器昏倒。

如果数据结构不能表示为有向无环图,那么我不确定您是否能够找到一种通用的深度克隆方法。循环图提供了许多棘手的极端情况,而且由于它不是一个常见的操作,我怀疑有人写过完整的解决方案(如果有可能的话 - 可能不会!但我现在没有时间尝试编写严格的证明。)。我在这个页面上发现了一些关于这个问题的很好的评论

如果您需要具有循环引用的对象数组的深层副本,我相信您将不得不编写自己的方法来处理您的专用数据结构,使其成为多通道克隆:

  1. 在第一轮,克隆所有不引用数组中其他对象的对象。跟踪每个对象的起源。
  2. 在第二轮中,将对象链接在一起。
@PatrickdeKleijn 答案的固定链接:web.archive.org/web/20140222022056/http ://my.opera.com/...
2021-03-12 04:37:25

如果你只需要一个克隆,最好的方法是做这个克隆:

使用... ES6扩展运算符。

这是最简单的例子:

var clonedObjArray = [...oldObjArray];

通过这种方式,我们将数组扩展为单独的值,并使用 [] 运算符将其放入一个新数组中。

这是一个更长的示例,显示了它的不同工作方式:

let objArray = [ {a:1} , {b:2} ];

let refArray = objArray; // this will just point to the objArray
let clonedArray = [...objArray]; // will clone the array

console.log( "before:" );
console.log( "obj array" , objArray );
console.log( "ref array" , refArray );
console.log( "cloned array" , clonedArray );

objArray[0] = {c:3};

console.log( "after:" );
console.log( "obj array" , objArray ); // [ {c:3} , {b:2} ]
console.log( "ref array" , refArray ); // [ {c:3} , {b:2} ]
console.log( "cloned array" , clonedArray ); // [ {a:1} , {b:2} ]

为了跟进@ToivoSäwén 所说的,这不会深度复制数组中的对象。它仍然会引用原始对象,所以如果你改变它们,它也会影响原始数组。
2021-03-22 04:37:25
好的现代答案,不适用于旧浏览器(如 IE 11)
2021-03-24 04:37:25
这只是深度复制数组,而不是数组中的每个对象。
2021-03-25 04:37:25
@Jealie 我猜KingpinEX 的目标是这个答案,让人们将 es6 转译为更普遍有用的 Babel 或你有什么。
2021-04-04 04:37:25
它仅适用于基元。试试这个:objArray[0].a = 3; 你会看到对象的引用在 clonedArray 中保持不变。
2021-04-05 04:37:25