Comment cloner un ensemble d'objets en version javascript?

… où chaque objet a également des références à d'autres objets dans le même tableau?

Quand j'ai abordé ce problème pour la première fois, j'avais quelque chose comme

var clonedNodesArray = nodesArray.clone() 

Existait et recherchait des informations sur la façon de cloner des objets en javascript. J'ai trouvé une question sur StackOverflow (répondu par le même @JohnResig) et il a souligné qu'avec jQuery, vous pourriez faire

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

Pour cloner un objet. J'ai essayé, cependant, cela ne fait que copier les références des objets dans le tableau. Donc, si je

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

La valeur des deux nœudsArray [0] et clonedNodesArray [0] sera "verte". Ensuite, j'ai essayé

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

Qui copie en profondeur un Objet, mais j'ai eu trop de récursivité et de " contrôle des débordements de pile " des messages de Firebug et Opera Dragonfly respectivement.

Comment le feriez-vous? Est-ce quelque chose qui ne devrait même pas être fait? Existe-t-il une manière réutilisable de le faire en Javascript?

Le problème avec votre copie superficielle est que tous les objets ne sont pas clonés. Alors que les références à chaque objet sont uniques dans chaque matrice, une fois que vous l'utilisez finalement, vous rencontrez le même objet qu'avant. Il n'y a rien de mal à la façon dont vous l'avez cloné … le même résultat se produirait en utilisant Array.slice ().

La raison pour laquelle votre copie profonde a des problèmes est parce que vous vous retrouvez avec des références d'objets circulaires. Deep va aussi profond que possible, et si vous avez un cercle, il continuera jusqu'à ce que le navigateur se fane.

Si la structure de données ne peut pas être représentée sous la forme d'un graphique acyclique dirigé, je ne suis pas sûr de pouvoir trouver une méthode polyvalente pour le clonage profond. Les graphiques cycliques fournissent de nombreux cas d'angle difficiles, et comme ce n'est pas une opération commune, je doute que quelqu'un ait écrit une solution complète (si cela est possible, ce n'est peut-être pas le cas! Mais je n'ai pas le temps d'essayer d'écrire une preuve rigoureuse maintenant). J'ai trouvé de bons commentaires sur le problème sur cette page .

Si vous avez besoin d'une copie en profondeur d'une Array of Objects avec des références circulaires, je crois que vous devrez coder votre propre méthode pour gérer votre structure de données spécialisée, de sorte qu'il s'agisse d'un clone multi-pass:

  1. Sur le premier tour, créez un clone de tous les objets qui ne font référence à d'autres objets dans le tableau. Gardez une trace des origines de chaque objet.
  2. Au deuxième tour, reliez les objets ensemble.

Allez les gars, c'est le 21ème siècle: tant que vos objets contiennent du contenu sérialisable JSON (pas de fonctions, ni Number.POSITIVE_INFINITY , etc.), il n'y a pas besoin de boucles pour cloner des tableaux ou des objets. Voici une solution pure à une ligne de vanille.

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

Pour résumer les commentaires ci-dessous, l'avantage principal de cette approche est qu'il clone également le contenu du tableau, pas seulement le tableau lui-même. Les inconvénients primaires sont la limite de ne fonctionner que sur le contenu sérialisable JSON, et sa performance (ce qui est nettement pire que l'approche basée sur une slice ).

Si tout ce dont vous avez besoin est une copie peu profonde, une façon vraiment simple est:

 new_array = old_array.slice(0); 

J'ai résolu le clonage d'un ensemble d'objets avec Object.assign

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

Il suffit de cloner n'importe quel type de tableau avec:

 [].concat(data); 

Ou, car concat peut ne pas fonctionner dans certains navigateurs IE, vous pouvez utiliser ceci:

 data.slice(0); 
 $.evalJSON($.toJSON(origArray)); 

Cela fonctionne pour moi:

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

Et si vous avez besoin d'une copie en profondeur d'objets dans le tableau:

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

La meilleure et la plus récente façon de faire ce clone est la suivante:

Utilisation de l'opérateur "…" ES6 spread.

Voici le plus simple Exemple:

 var clonedObjArray = [...oldObjArray]; 

De cette façon, nous réparons le tableau en valeurs individuelles et le mettons dans un nouveau tableau avec l'opérateur [].

Voici un exemple plus long qui montre les différentes manières de fonctionner:

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

Je peux avoir un moyen simple de le faire sans avoir à faire une récurrence douloureuse et ne pas connaître tous les détails plus fins de l'objet en question. À l'aide de jQuery, il suffit de convertir votre objet en JSON en utilisant jQuery $.toJSON(myObjectArray) , puis prenez votre chaîne JSON et évaluez-le à nouveau sur un objet. BAM! Fait et fait! Problème résolu. 🙂

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

Je réponds à cette question parce qu'il ne semble pas y avoir de solution simple et explicite au problème du "clonage d'un ensemble d'objets en 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 

Cette solution itère les valeurs du tableau, puis itère les clés de l'objet, en la sauvegardant dans un nouvel objet, puis en poussant ce nouvel objet vers un nouveau tableau.

Voir jsfiddle . Remarque: un simple .slice() ou [].concat() n'est pas suffisant pour les objets dans le tableau.

Comme l'a mentionné Daniel Lew, les graphiques cycliques ont des problèmes. Si j'avais ce problème, j'ajouterais des méthodes clone() spéciales aux objets problématiques ou je me souviendrais des objets que j'ai déjà copiés.

Je le ferais avec un copyCount variable qui augmente de 1 chaque fois que vous copiez dans votre code. Un objet qui a un copyCount inférieur que le processus de copie actuel est copié. Sinon, la copie, qui existe déjà, devrait être référencée. Cela oblige à lier l'original à sa copie.

Il y a encore un problème: la mémoire. Si vous avez cette référence d'un objet à l'autre, il est probable que le navigateur ne peut pas libérer ces objets, car ils sont toujours référencés par quelque part. Vous devriez faire une deuxième passe où vous avez défini toutes les références de copie sur Null. (Si vous faites cela, vous ne devriez pas avoir un copyCount mais un boolean isCopied suffirait, car vous pouvez réinitialiser la valeur dans la deuxième passe.)

JQuery extension fonctionne bien, il suffit de spécifier que vous cloquez un tableau plutôt qu'un objet ( notez le [] au lieu de {} comme paramètre dans la méthode d'extension ):

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

Array.slice peut être utilisé pour copier un tableau ou une partie d'un tableau .. http://www.devguru.com/Technologies/Ecmascript/Quickref/Slice.html Cela fonctionnerait avec des chaînes et des numéros … – changer une chaîne dans Un tableau n'affecterait pas l'autre – mais les objets sont encore simplement copiés par référence, de sorte que les modifications apportées aux objets référencés dans un tableau affecteraient l'autre tableau.

Voici un exemple d'un gestionnaire d'annulation JavaScript qui pourrait être utile pour cela: http://www.ridgway.co.za/archive/2007/11/07/simple-javascript-undo-manager-for-dtos.aspx

J'ai été très frustré par ce problème. Apparemment, le problème survient lorsque vous envoyez un tableau générique à la méthode $ .extend. Donc, pour le réparer, j'ai ajouté un petit contrôle, et cela fonctionne parfaitement avec les tableaux génériques, les tableaux jQuery et tous les objets.

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

Invoquer en utilisant:

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

Ma démarche:

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

Me donne un clone agréable, propre et profond du tableau d'origine – sans aucun des objets référencés à l'original 🙂

Oubliez eval () (est la fonctionnalité la plus utilisée de JS et rend le code lent) et tranche (0) (fonctionne uniquement pour les types de données simples)

C'est la meilleure solution pour moi:

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

Nous pouvons inventer une simple méthode Array récursive pour cloner des tableaux multidimensionnels. Alors que les objets dans les tableaux imbriqués gardent leur référence aux objets correspondants dans le tableau source, les tableaux ne le seront pas.

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

Avec jQuery:

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

Le code suivant effectuera récursivement une copie profonde d'objets et de tableau :

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

La source

Cette profonde copie des tableaux, des objets, des valeurs scales et nulles, et copie profondément toutes les propriétés sur les fonctions non natives (ce qui est assez rare mais possible). (Pour plus d'efficacité, nous n'essayons pas de copier des propriétés non numériques sur des tableaux.)

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

Je pense que j'ai réussi à écrire une méthode générique de clonage profond de toute structure JavaScript principalement en utilisant Object.create qui est pris en charge dans tous les navigateurs modernes. Le code est comme ceci:

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

Pour le clonage des objets, je proposais simplement que ECMAScript 6 reduce() :

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

Mais franchement, j'aime la réponse de @dinodsaurus encore mieux. Je pose juste cette version ici comme une autre option, mais personnellement j'utiliserai map() comme suggéré par @dinodsaurus.

Selon si vous avez Underscore ou Babel, voici une référence de la manière différente de clonage profond d'un tableau.

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

On dirait que babel est le plus rapide.

 var x = babel({}, obj) 

Quelques manières élégantes pour le clonage profond en javascript

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

1) Une méthode vanilla Javascript pour clonage d'objets

2) Un exploit intelligent de la bibliothèque JSON aux objets clones profonds

3) En utilisant la fonction $ .extend () de jQuery

4) Utilisation de la fonction clone () de Mootools pour cloner des objets

Cette méthode est très simple et vous pouvez modifier votre clone sans modifier le tableau d'origine.

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