您如何在Javascript中克隆对象数组?


420

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

当我第一次想到这个问题时

var clonedNodesArray = nodesArray.clone()

将存在并搜索有关如何在javascript中克隆对象的信息。我确实在StackOverflow上发现了一个问题(由同样的@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 得到了“ 太多的递归 ”和“ 控制堆栈溢出 ”消息。

你会怎么做?这是什至不应该做的事情吗?有没有一种可重用的方式来做到这一点在Javascript中?

Answers:


106

您的浅表副本的问题在于未克隆所有对象。虽然每个对象的引用在每个数组中都是唯一的,但是一旦最终抓住它,您将像以前一样处理同一对象。克隆它的方式没有错……使用Array.slice()会产生相同的结果。

您的深层副本有问题的原因是因为您最终使用了循环对象引用。Deep会尽可能深入,如果您有一个圆圈,它会一直无限前进,直到浏览器晕倒为止。

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

如果您需要带有循环引用的对象数组的深层副本,我相信您将必须编写自己的方法来处理您的专用数据结构,例如多遍克隆:

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


531

只要您的对象包含JSON可序列化的内容(没有函数,no Number.POSITIVE_INFINITY等),就不需要任何循环来克隆数组或对象。这是纯香草的单线解决方案。

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

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


118
这可能适用于JSON数据,但是如果您的数组包含具有函数的对象的任何函数或实例,请对它们说再见。
sp0rkyd0rky

12
如果您有一个包含值Infinity的数组,请小心。该值丢失(此后为空)。(jsfiddle.net/klickagent/ehm4bd3s
klickagent.ch

13
除非您的数组仅包含原语,和/或本身仅包含字符串/数字/布尔原语的对象,否则这通常是一种不好的方法(即使null而且undefined也会出现问题,因为JSON不支持它们)。此外,它的运行效率远远低于old_array.slice(0);,应该会越来越好。
XML

2
如果数组的对象具有DateTime,则将返回字符串而不是DateTime!new Date!== JSON.parse(JSON.stringify(new Date))
MarkosyanArtur

2
OP的问题的关键点(上面的答案完全忽略了这一点):...每个对象在同一数组中还具有对其他对象的引用?
XML

288

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

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

甚至更短的传播语法

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

15
但是,如果myArray包含一堆恐龙,则newArray包含一堆对象。la脚,你不同意吗?
马修·詹姆斯·戴维斯

3
最好的方法,因为它可以使对象函数保持活动状态,然后尝试用JSON.parse(JSON.stringify(nodesArray))丢掉它们
Scipper

14
@MatthewJamesDavis,您可以通过替换来解决此问题 {}new Dinosaur()
Agargara

5
浅表复制而不是深表复制
苏丹伊斯兰堡

1
如果对象仅包含原始属性,这对对象数组很有用...这就是我需要的,谢谢
mojave

154

如果您需要的只是浅表副本,那么一个真正简单的方法是:

new_array = old_array.slice(0);

6
我认为您不必通过0.slice()无论如何都可以至少拨打chrome 通话
slf

112
但这实际上不起作用,对吗?我的意思是,这不是对如何克隆对象数组的问题的答案。这是克隆简单数组的解决方案。
bozdoz

35
实际上,这不适用于对象数组。返回的数组by slice将是一个新数组,但将包含对原始数组对象的引用。
Sergio A.

4
这仅适用于“泛型” int,字符串等,不适用于对象数组。
Stefan Michev 2014年

5
对于实际上不会克隆的对象数组,更新为new_array也会更新old_array。
阿纳斯

43

进行此克隆的最佳方法和最新方法如​​下:

使用...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} ]


2
好的现代答案,不适用于较旧的浏览器(例如IE 11)
Jealie

1
@Jealie我猜想KingpinEX的目标是针对将es6移植到Babel或您所拥有的更普遍有用的东西的人们。
ruffin

61
这只是深度复制数组,而不是数组中的每个对象。
ToivoSäwén17年

31
若要继续@ToivoSäwén所说的话,这不会深度复制数组中的对象。它仍然会引用原始对象,因此,如果您对它们进行突变,也会影响原始数组。
Joel Kinzel

3
它仅适用于基本体。试试这个:objArray [0] .a = 3; 并且您会看到对象的引用在clonedArray中保持不变。
塞尔吉奥·科雷亚

25

这对我有用:

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

而且,如果您需要数组中对象的深层副本:

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

1
这看起来像它会工作。我试图避免广泛使用jQuery,因此在我的情况下不会使用它,但是for循环和for ... in可以使用。
bozdoz

19
$.evalJSON($.toJSON(origArray));

2
您将需要使用jquery json插件才能使用此 code.google.com/p/jquery-json
wmitchell 2011年

32
没有JQ(在现代浏览器中很好):JSON.parse(JSON.stringify(origArray));
forresto

我发现此评论很有用。在我的实现中,我需要复制应用了KnockoutJS可观察属性的对象数组。副本仅需要值,而不需要可观察性。为了复制值,我使用了JSON.parse(ko.toJSON(origArray))或ko.utils.parseJson(ko.toJSON(origArray))。只需2美分,谢谢您帮助我达成解决方案。
wavedrop

6
JSON.parse(JSON.stringify(origArray));绝对是最简单的解决方案。
yorkw 2013年

jQuery通常是不必要的。youmightnotneedjquery.com
ADJenks,

9

Map将从旧的数组创建新数组(不引用旧数组),然后在地图内部创建新对象并遍历属性(键),并将旧Array对象中的值分配给新对象的coresponding属性。

这将创建完全相同的对象数组。

let newArray = oldArray.map(a => {
               let newObject = {};
               Object.keys(a).forEach(propertyKey => {
                    newObject[propertyKey] = a[propertyKey];
               });
               return newObject ;
});

8

我可能有一种简单的方法来执行此操作,而不必进行痛苦的递归并且不知道所讨论对象的所有更详细的信息。使用jQuery,只需使用jQuery将您的对象转换为JSON $.toJSON(myObjectArray),然后获取您的JSON字符串并将其求值返回给对象即可。AM!做完了!问题解决了。:)

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

21
一些现代的浏览器具有内置的JSON方法,因此您可以执行以下操作:JSON.parse(JSON.stringify(MY_ARRAY))应该更快。好建议。
Nicolas R

1
如果他们不使用json2那就不行eval
kamranicus 2012年

这具有可怕的性能,但不幸的是,这是我见过的最好的答案:/
Dvid Silva

不要评估用户数据。最好不要使用eval()。这是安全隐患。
ADJenks

8

我之所以要回答这个问题,是因为似乎没有一个简单而明确的解决方案来解决“在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

此解决方案迭代数组值,然后迭代对象键,将对象键保存到新对象,然后将该新对象推送到新数组。

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


感谢您提供答案,但是您应该强调答案的缺点。当对象中有对象时,它不起作用..对吗?
严苛的

它将创建浅表副本。不深入
苏丹伊斯兰教

您需要在某处添加递归
DGoiko '19

6

jQuery扩展工作正常,只需要指定要克隆的是数组而不是对象(请注意,请使用[]而不是{}作为extend方法的参数):

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

2
嗯,如果您对此表示不赞成,可以请您添加注释,说明为什么这样做吗?还是可以先尝试一下代码,看看它是否有效?谢谢;)
Stef

1
更改第一个数组中的对象后,第二个数组中的对象将被修改,因此不可行。
Spikolynn

6

此方法非常简单,您可以修改克隆而不修改原始数组。

// 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'}]


1
这确实浅拷贝这两个层次深,而[...oldArray]oldArray.slice(0)做浅复制一个级别深度。因此,这非常有用,但不是真正的完整深度克隆。
本·惠勒

真正的深度克隆可以使用lodash.clonedeepnpm 来完成
启示录

5

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

我会使用一个变量copyCount,每次在代码中复制时该变量都会增加1。较低的对象copyCount复制比当前复制过程。如果没有,应参考已经存在的副本。这使得有必要从原始链接到其副本。

仍然存在一个问题:内存。如果您具有从一个对象到另一个对象的引用,则浏览器可能无法释放这些对象,因为它们总是从某个地方引用的。您必须第二遍,将所有副本引用都设置为Null。(如果您这样做,则不必copyCount但布尔值isCopied就足够了,因为您可以在第二遍中重置该值。)


4

Array.slice可用于复制一个数组或数组的一部分。http://www.devguru.com/Technologies/Ecmascript/Quickref/Slice.html 这将适用于字符串和数字..- 更改字符串一个数组不会影响另一个数组-但是对象仍然只是按引用复制,因此对一个数组中被引用对象的更改将对另一个数组产生影响。

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


我知道。之所以要实现此目的,是因为我正尝试通过回溯解决CSP问题。我认为实现回溯的一种方式可能类似于通过将快照复制到堆栈中来“获取快照”变量的分配状态。
wallyqs

...而且,实际上这可能不是一个好主意。
wallyqs

这种方法可能会带来其他同步复杂性:) ..您如何知道在拍摄快照未更改阵列?
markt

添加了指向文章的链接,其中作者使用javascript实现了一个简单的撤消管理器
。– markt

4

我的方法:

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

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


这是使用jquery的最佳解决方案。简短而甜美。
约翰·亨克尔

1
我进行了性能测试,该解决方案似乎比JSON.stringify解决方案快大约2倍。
meehocz

4

lodash具有以下cloneDeep功能:

var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);

4

如果要实现深度克隆,请使用JSON.parse(JSON.stringify(your {}或[]))

const myObj ={
    a:1,
    b:2,
    b:3
}

const deepClone=JSON.parse(JSON.stringify(myObj));
deepClone.a =12;
console.log("deepClone-----"+myObj.a);
const withOutDeepClone=myObj;
withOutDeepClone.a =12;
console.log("withOutDeepClone----"+myObj.a);


3

忘记eval()(这是JS最易滥用的功能,会使代码变慢)和slice(0)(仅适用于简单数据类型)

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

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;
};

3

这个问题让我感到非常沮丧。当您将通用数组发送到$ .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);

深克隆?我使用的是jquery-1.9.1,它不支持此方法。是更现代的版本的方法吗?
user5260143

@ user2783091他正在扩展JQuery以添加该功能。它不是开箱即用的东西
JorgeeFG 2014年

3

这会深深地复制数组,对象,空值和其他标量值,并且还会深深地复制非本机函数上的所有属性(这很罕见,但可能)。(为了提高效率,我们不尝试在数组上复制非数字属性。)

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;
}

3

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

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

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

我们还可以使用此语法,该语法相同但更短:

let newObject = [...oldObject];

请注意,这些方法将仅复制对数组和数组中对象的引用,而不会为它们创建新副本。期望这会破坏多维结构。
本·惠勒

2

我们可以发明一种简单的递归数组方法来克隆多维数组。虽然嵌套数组中的对象保留对源数组中相应对象的引用,但数组不会。

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));


2

在JavaScript中,数组和对象复制会更改原始值,因此深度复制是解决此问题的方法。

深层复制实际上意味着创建一个新数组并复制值,因为发生的任何事情都不会影响原始数组。

JSON.parse并且JSON.stringify是进行深度复制的最佳和简单方法。该JSON.stringify()方法将JavaScript值转换为JSON.parse()JSON字符串。该方法解析JSON字符串,构造JavaScript值或该字符串描述的对象。

//深克隆

let a = [{ x:{z:1} , y: 2}];
let b = JSON.parse(JSON.stringify(a));
b[0].x.z=0

console.log(JSON.stringify(a)); //[{"x":{"z":1},"y":2}]
console.log(JSON.stringify(b)); // [{"x":{"z":0},"y":2}]

有关更多详细信息:在这里阅读


1
这是最好的解决方案。谢谢。
Nikolay

1

使用jQuery:

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

1

以下代码将递归执行对象和数组深层复制

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;
}

资源


arguments.callee在严格模式下不可用,否则会出现性能问题。
布雷特·扎米尔


0

我认为设法编写了一种通用方法来深克隆任何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;
}

Object.create将被item视为对象的原型,但这与克隆不同。如果item被修改,更改将反映在其“克隆”中,反之亦然。这种方法行不通。
Brett Zamir

0

对于克隆对象,我只是建议ECMAScript 6 reduce()

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

但坦率地说,我更喜欢@dinodsaurus的答案。我只是将此版本放在这里作为其他选择,但就我个人而言,我将按照map()@dinodsaurus的建议使用。



0
       var game_popularity = [
            { game: "fruit ninja", popularity: 78 },
            { game: "road runner", popularity: 20 },
            { game: "maze runner", popularity: 40 },
            { game: "ludo", popularity: 75 },
            { game: "temple runner", popularity: 86 }
        ];
        console.log("sorted original array before clonning");
        game_popularity.sort((a, b) => a.popularity < b.popularity);
        console.log(game_popularity);


        console.log("clone using object assign");
        const cl2 = game_popularity.map(a => Object.assign({}, a));
        cl2[1].game = "clash of titan";
        cl2.push({ game: "logan", popularity: 57 });
        console.log(cl2);


        //adding new array element doesnt reflect in original array
        console.log("clone using concat");
        var ph = []
        var cl = ph.concat(game_popularity);

        //copied by reference ?
        cl[0].game = "rise of civilization";

        game_popularity[0].game = 'ping me';
        cl.push({ game: "angry bird", popularity: 67 });
        console.log(cl);

        console.log("clone using ellipses");
        var cl3 = [...game_popularity];
        cl3.push({ game: "blue whale", popularity: 67 });
        cl3[2].game = "harry potter";
        console.log(cl3);

        console.log("clone using json.parse");
        var cl4 = JSON.parse(JSON.stringify(game_popularity));
        cl4.push({ game: "home alone", popularity: 87 });
        cl4[3].game ="lockhead martin";
        console.log(cl4);

        console.log("clone using Object.create");
        var cl5 = Array.from(Object.create(game_popularity));
        cl5.push({ game: "fish ville", popularity: 87 });
        cl5[3].game ="veto power";
        console.log(cl5);


        //array function
        console.log("sorted original array after clonning");
        game_popularity.sort((a, b) => a.popularity < b.popularity);
        console.log(game_popularity);


        console.log("Object.assign deep clone object array");
        console.log("json.parse deep clone object array");
        console.log("concat does not deep clone object array");
        console.log("ellipses does not deep clone object array");
        console.log("Object.create does not deep clone object array");


        Output:


        sorted original array before clonning
        [ { game: 'temple runner', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'ludo', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 } ]
        clone using object assign
        [ { game: 'temple runner', popularity: 86 },
        { game: 'clash of titan', popularity: 78 },
        { game: 'ludo', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'logan', popularity: 57 } ]
        clone using concat
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'ludo', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'angry bird', popularity: 67 } ]
        clone using ellipses
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'blue whale', popularity: 67 } ]
        clone using json.parse
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'lockhead martin', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'home alone', popularity: 87 } ]
        clone using Object.create
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'veto power', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'fish ville', popularity: 87 } ]
        sorted original array after clonning
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'veto power', popularity: 40 },
        { game: 'road runner', popularity: 20 } ]

        Object.assign deep clone object array
        json.parse deep clone object array
        concat does not deep clone object array
        ellipses does not deep clone object array
        Object.create does not deep clone object array
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.