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

…每个对象还有对同一个数组内其他对象的引用吗?

当我第一次想到这个问题时,我只是想了解一些事情

var clonedNodesArray = nodesArray.clone() 

将存在并search有关如何在JavaScript中克隆对象的信息。 我确实在StackOverflow上find了一个问题 (由同样的@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); 

它深度复制一个对象,但是我分别从Firebug和Opera Dragonfly那里得到了“ 太多的recursion ”和“ 控制堆栈溢出 ”的消息。

你会怎么做? 这是不应该做的事吗? 有没有一种可重复使用的方式在JavaScript中做到这一点?

你的浅拷贝的问题是所有的对象都没有被克隆。 虽然每个对象的引用在每个数组中都是唯一的,但是一旦最终抓到它,就会像以前一样处理同一个对象。 你克隆它的方式没有什么错误…使用Array.slice()会发生相同的结果。

你的深拷贝有问题的原因是因为你最后是循环的对象引用。 深度将尽可能深,如果你有一个圈子,它会一直持续下去,直到浏览器昏了过去。

如果数据结构不能被表示成一个有向的非循环图,那么我不确定你将能够find一个通用的深度克隆方法。 循环图提供了许多棘手的angular落案例,由于这不是一个常见的操作,所以我怀疑有人写了一个完整的解决scheme(如果甚至可能的话 – 可能不会!)但我现在没有时间去写一个严格的certificate。 我在这个页面上发现了一些关于这个问题的好评。

如果你需要一个具有循环引用的对象数组的深层副本,我相信你将不得不编写你自己的方法来处理你的专门的数据结构,这样它是一个多遍的克隆:

  1. 在第一步,制作一个不引用数组中其他对象的所有对象的克隆。 跟踪每个对象的来源。
  2. 在第二轮,将这些对象链接在一起。

来吧,这是21世纪:只要你的对象包含JSON序列化的内容(没有函数,没有Number.POSITIVE_INFINITY等),不需要任何循环克隆数组或对象。 这是一个纯粹的香草单线解决scheme。

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

总结下面的评论,这种方法的主要优点是它也克隆数组的内容,而不仅仅是数组本身。 主要的缺点是只能处理JSON序列化的内容,而且性能很差(比基于分slice的方法要差得多)。

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

 new_array = old_array.slice(0); 

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

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

甚至更短的扩展语法

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

简单地克隆任何types的数组:

 [].concat(data); 

或者,因为concat可能无法在一些IE浏览器中工作,所以可以使用这个:

 data.slice(0); 

这适用于我:

 var clonedArray = $.map(originalArray, function (obj) { return $.extend({}, obj); }); 

如果你需要深入复制数组中的对象:

 var clonedArray = $.map(originalArray, function (obj) { return $.extend(true, {}, obj); }); 
 $.evalJSON($.toJSON(origArray)); 

最好的和最新的方式来做这个克隆如下:

使用“…”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} ] 

我可能有一个简单的方法来做到这一点,而不必做痛苦的recursion,不知道所讨论的对象的所有细节。 使用jQuery,只需使用jQuery $.toJSON(myObjectArray)将对象转换为JSON,然后取出JSONstring并将其评估回​​给对象。 BAM! 完成,完成! 问题解决了。 🙂

 var oldObjArray = [{ Something: 'blah', Cool: true }]; var newObjArray = eval($.toJSON(oldObjArray)); 

我正在回答这个问题,因为似乎没有一个简单而明确的解决scheme来解决“在Javascript中复制对象数组”的问题:

 function deepCopy (arr) { var out = []; for (var i = 0, len = arr.length; i < len; i++) { var item = arr[i]; var obj = {}; for (var k in item) { obj[k] = item[k]; } out.push(obj); } return out; } // test case var original = [ {'a' : 1}, {'b' : 2} ]; var copy = deepCopy(original); // change value in copy copy[0]['a'] = 'not 1'; // original[0]['a'] still equals 1 

此解决scheme迭代数组值,然后迭代对象键,将后者保存到一个新的对象,然后将该新的对象推到一个新的数组。

见jsfiddle 。 注意:一个简单的.slice()[].concat()对于数组的对象是不够的。

正如Daniel Lew所说,循环图有一些问题。 如果我遇到了这个问题,我可以将特殊的clone()方法添加到有问题的对象中,或者记住我已经复制了哪些对象。

我会做一个variablescopyCount ,每次复制代码时增加1。 复制具有比当前复制过程更低的copyCount的对象。 如果不是,则应该引用已存在的副本。 这使得有必要链接从原来的副本。

还有一个问题:内存。 如果您从一个对象到另一个对象的引用,那么浏览器可能无法释放这些对象,因为它们总是从某处引用。 您必须进行第二遍,将所有复制引用设置为Null。 (如果你这样做,你不必有一个copyCount但布尔isCopied就足够了,因为你可以在第二遍重置该值。)

Array.slice可以用来复制一个数组或数组的一部分.. http://www.devguru.com/Technologies/Ecmascript/Quickref/Slice.html这将与string和数字;.. – 更改string一个数组不会影响另一个数组 – 但是对象仍然是通过引用复制的,所以对一个数组中引用对象的更改会影响另一个数组。

下面是一个JavaScript撤消pipe理器的例子,可能对此有用: http : //www.ridgway.co.za/archive/2007/11/07/simple-javascript-undo-manager-for-dtos.aspx

我的方法是:

 var temp = { arr : originalArray }; var obj = $.extend(true, {}, temp); return obj.arr; 

给了我一个很好,干净,原始数组的深层克隆 – 没有任何对象引用回原来的:-)

JQuery的扩展工作正常,只需要指定你正在克隆一个数组而不是一个对象( 注意[]而不是{}作为扩展方法的参数 ):

 var clonedNodesArray = jQuery.extend([], nodesArray); 

我非常沮丧的这个问题。 显然,当您向通用数组发送$ .extend方法时会出现问题。 所以,为了解决这个问题,我添加了一些检查,它可以与通用数组,jQuery数组和任何对象完美协作。

 jQuery.extend({ deepclone: function(objThing) { // return jQuery.extend(true, {}, objThing); /// Fix for arrays, without this, arrays passed in are returned as OBJECTS! WTF?!?! if ( jQuery.isArray(objThing) ) { return jQuery.makeArray( jQuery.deepclone($(objThing)) ); } return jQuery.extend(true, {}, objThing); }, }); 

调用使用:

 var arrNewArrayClone = jQuery.deepclone(arrOriginalArray); // Or more simply/commonly var arrNewArrayClone = $.deepclone(arrOriginalArray); 

忘记eval()(是JS中被滥用最多的特性,并使代码变慢)和slice(0)(仅适用于简单的数据types)

这对我来说是最好的解决scheme:

 Object.prototype.clone = function() { var myObj = (this instanceof Array) ? [] : {}; for (i in this) { if (i != 'clone') { if (this[i] && typeof this[i] == "object") { myObj[i] = this[i].clone(); } else myObj[i] = this[i]; } } return myObj; }; 

我们可以创build一个简单的recursion数组方法来克隆multidimensional array。 虽然嵌套数组内的对象保持对源数组中对应对象的引用,但数组不会。

 Array.prototype.clone = function(){ return this.map(e => Array.isArray(e) ? e.clone() : e); }; var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ], brr = arr.clone(); brr[4][2][1] = "two"; console.log(JSON.stringify(arr)); console.log(JSON.stringify(brr)); 

这个方法非常简单,你可以在不修改原始数组的情况下修改你的克隆。

 // Original Array let array = [{name: 'Rafael'}, {name: 'Matheus'}]; // Cloning Array let clone = array.map(a => {return {...a}}) // Editing the cloned array clone[1].name = 'Carlos'; console.log('array', array) // [{name: 'Rafael'}, {name: 'Matheus'}] console.log('clone', clone) // [{name: 'Rafael'}, {name: 'Carlos'}] 

我使用新的ECMAScript 6 Object.assign方法:

 let oldObject = [1,3,5,"test"]; let newObject = Object.assign({}, oldObject); 

这个方法的第一个参数是要更新的数组,我们传递一个空对象,因为我们想要一个新的对象。

我们也可以使用这个相同但是更短的语法:

 let newObject = [...oldObject]; 

与jQuery:

 var target= []; $.each(source, function() {target.push( $.extend({},this));}); 

下面的代码将recursion执行对象和数组的深层复制 :

 function deepCopy(obj) { if (Object.prototype.toString.call(obj) === '[object Array]') { var out = [], i = 0, len = obj.length; for ( ; i < len; i++ ) { out[i] = arguments.callee(obj[i]); } return out; } if (typeof obj === 'object') { var out = {}, i; for ( i in obj ) { out[i] = arguments.callee(obj[i]); } return out; } return obj; } 

资源

这深度复制了数组,对象,null和其他标量值,并且深度复制了非本地函数的任何属性(这是非常罕见的但可能的)。 (为了提高效率,我们不尝试复制数组的非数字属性。)

 function deepClone (item) { if (Array.isArray(item)) { var newArr = []; for (var i = item.length; i-- > 0;) { newArr[i] = deepClone(item[i]); } return newArr; } if (typeof item === 'function' && !(/\(\) \{ \[native/).test(item.toString())) { var obj; eval('obj = '+ item.toString()); for (var k in item) { obj[k] = deepClone(item[k]); } return obj; } if (item && typeof item === 'object') { var obj = {}; for (var k in item) { obj[k] = deepClone(item[k]); } return obj; } return item; } 

我想设法写一个通用的方法深入克隆任何JavaScript结构主要使用Object.create所支持的所有现代浏览器。 代码是这样的:

 function deepClone (item) { if (Array.isArray(item)) { var newArr = []; for (var i = item.length; i-- !== 0;) { newArr[i] = deepClone(item[i]); } return newArr; } else if (typeof item === 'function') { eval('var temp = '+ item.toString()); return temp; } else if (typeof item === 'object') return Object.create(item); else return item; } 

为了克隆对象,我只是build议ECMAScript 6 reduce()

 const newArray=myArray.reduce((array, element)=>array.push(Object.assign({}, element)), []); 

但坦率地说,我更喜欢@dinodsaurus的答案。 我只是在这里把这个版本作为另一个选项,但是我个人会使用@dinodsaurusbuild议的map()

取决于如果你有下划线或巴别这里是深度克隆数组的不同方式的基准。

https://jsperf.com/object-rest-spread-vs-clone/2

看起来巴贝尔是最快的。

 var x = babel({}, obj) 

一些优雅的方式深入克隆在JavaScript

http://heyjavascript.com/4-creative-ways-to-clone-objects/#

1)用于克隆对象的vanilla Javascript方法

2)巧妙利用JSON库来深入克隆对象

3)使用jQuery的$ .extend()函数

4)使用Mootools的clone()函数克隆对象