Comment cloner correctement un objet JavaScript?
j'ai un objet", 151900920" . J'aimerais le copier comme objet y
, de sorte que les changements à y
ne modifient pas x
. J'ai réalisé que copier des objets dérivés d'objets JavaScript intégrés entraînera des propriétés supplémentaires et indésirables. Ce n'est pas un problème, puisque je copie un de mes propres objets, littéralement construits.
comment cloner correctement un objet JavaScript?
30 réponses
pour faire cela pour tout objet en JavaScript ne sera pas simple ou simple. Vous rencontrerez le problème de récupérer par erreur des attributs du prototype de l'objet qui devraient être laissés dans le prototype et non copiés dans la nouvelle instance. Si, par exemple, l'ajout d'un clone
méthode Object.prototype
, que certaines réponses représenter, vous devrez explicitement ignorer cet attribut. Mais qu'en est-il s'il y a d'autres méthodes supplémentaires ajoutées à Object.prototype
, ou d'autres des prototypes intermédiaires, que vous ne connaissez pas? Dans ce cas, vous copierez des attributs que vous ne devriez pas, vous devez donc détecter des attributs non locaux imprévus avec la méthode hasOwnProperty
.
en plus des attributs non-dénombrables, vous rencontrerez un problème plus difficile lorsque vous essayez de copier des objets qui ont des propriétés cachées. Par exemple, prototype
est une propriété cachée d'une fonction. De plus, le prototype d'un objet est référencé par le l'attribut __proto__
, qui est également caché, et ne sera pas copié par une boucle for/in itérant les attributs de l'objet source. Je pense que __proto__
pourrait être spécifique à L'interpréteur JavaScript de Firefox et il peut être quelque chose de différent dans d'autres navigateurs, mais vous obtenez l'image. Tout n'est énumérable. Vous pouvez copier un attribut caché si vous connaissez son nom, mais je ne connais aucun moyen de le découvrir automatiquement.
encore un autre accroc dans la quête d'un la solution élégante est le problème de configurer correctement l'héritage du prototype. Si le prototype de votre objet source est Object
, alors la création d'un nouvel objet général avec {}
fonctionnera, mais si le prototype de la source est un descendant de Object
, alors vous allez manquer les membres supplémentaires de ce prototype que vous avez sauté en utilisant le filtre hasOwnProperty
, ou qui étaient dans le prototype, mais qui n'étaient pas dénombrables en premier lieu. Une solution pourrait il faut appeler la propriété constructor
de l'objet source pour obtenir l'objet copie initial et ensuite copier les attributs, mais alors vous n'obtiendrez toujours pas d'attributs non-énumérables. Par exemple, un Date
objet stocke ses données comme un membre caché:
function clone(obj) {
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
}
return copy;
}
var d1 = new Date();
/* Executes function after 5 seconds. */
setTimeout(function(){
var d2 = clone(d1);
alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);
la chaîne de datation pour d1
sera 5 secondes derrière celle de d2
. Une façon de faire un Date
la même chose qu'un autre est en appelant le setTime
méthode, mais qui est spécifique à la classe Date
. Je ne pense pas qu'il existe une solution générale à ce problème, même si je serais heureux de me tromper!
quand j'ai dû mettre en œuvre la copie générale profonde, j'ai fini par faire des compromis en présumant que je n'aurais besoin de copier qu'une Object
, Array
, Date
, String
, Number
, ou Boolean
. Les 3 derniers types sont immuables, donc je pourrais effectuer une copie superficielle et ne pas s'inquiéter sur elle en train de changer. J'ai supposé en outre que tout élément contenu dans Object
ou Array
serait aussi l'un des 6 types simples dans cette liste. Cela peut être accompli avec le code suivant:
function clone(obj) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
la fonction ci-dessus fonctionnera correctement pour les 6 types simples que j'ai mentionnés, aussi longtemps que les données dans les objets et les tableaux forment une structure d'arbre. Qui est, il n'y a pas plus d'une référence aux mêmes données de l'objet. Exemple:
// This would be cloneable:
var tree = {
"left" : { "left" : null, "right" : null, "data" : 3 },
"right" : null,
"data" : 8
};
// This would kind-of work, but you would get 2 copies of the
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
"left" : { "left" : null, "right" : null, "data" : 3 },
"data" : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];
// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
"left" : { "left" : null, "right" : null, "data" : 3 },
"data" : 8
};
cyclicGraph["right"] = cyclicGraph;
il ne sera pas en mesure de gérer tout objet JavaScript, mais il peut être suffisant pour de nombreuses fins aussi longtemps que vous ne supposez pas que cela fonctionnera juste pour tout ce que vous jetez à elle.
avec jQuery, vous pouvez copie peu profonde avec étendre :
var copiedObject = jQuery.extend({}, originalObject)
les modifications ultérieures de l'objet copié n'affecteront pas l'objet original, et vice versa.
ou pour faire une copie profonde :
var copiedObject = jQuery.extend(true, {}, originalObject)
si vous n'utilisez pas de fonctions dans votre objet, une doublure très simple peut être la suivante:
var cloneOfA = JSON.parse(JSON.stringify(a));
cela fonctionne pour tous les types d'objets contenant des objets, tableaux, cordes, booléens et numéros.
Voir aussi cet article à propos de la structuration de la clone algorithme de navigateurs qui est utilisé lors de l'envoi de messages vers et à partir d'un travailleur. Il contient également une fonction pour le clonage profond.
dans ECMAScript 6 Il y a objet.assignez la méthode , qui copie les valeurs de toutes les propriétés propres énumérables d'un objet à un autre. Par exemple:
var x = {myProp: "value"};
var y = Object.assign({}, x);
Mais sachez que les objets imbriqués sont toujours copiés comme référence.
Per MDN :
- si vous voulez une copie superficielle, utilisez
Object.assign({}, a)
- pour "copie profonde", utiliser
JSON.parse(JSON.stringify(a))
il n'y a pas besoin de bibliothèques externes, mais vous devez vérifier la compatibilité du navigateur d'abord .
il y a beaucoup de réponses, mais aucune qui mentionne objet.créez à partir D'ECMAScript 5, qui, il est vrai, ne vous donne pas une copie exacte, mais définit la source comme le prototype du nouvel objet.
Ainsi, ce n'est pas une réponse exacte à la question, mais c'est une solution en ligne et ainsi élégant. Et il fonctionne mieux pour 2 cas:
- Lorsqu'un tel héritage est utile (duh!)
- où le l'objet source ne sera pas modifié, faisant ainsi de la relation entre les 2 objets un problème.
exemple:
var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property
pourquoi cette solution me semble-t-elle supérieure? Il est natif, donc pas de boucle, pas de récursivité. Cependant, les navigateurs plus anciens auront besoin d'un polyfill.
une façon élégante de cloner un objet Javascript dans une ligne de code
une méthode Object.assign
fait partie de la norme ECMAScript 2015 (ES6) et fait exactement ce dont vous avez besoin.
var clone = Object.assign({}, obj);
L'Objet.assign() la méthode est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers un objet cible.
Le polyfill pour soutenir les anciens navigateurs:
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(nextSource);
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}
il y a plusieurs problèmes avec la plupart des solutions sur internet. Alors j'ai décidé de faire un suivi, qui comprend, pourquoi la réponse acceptée ne devrait pas être acceptée.
situation de départ
je veux copie en profondeur Javascript Object
avec tous ses enfants et leurs enfants et ainsi de suite. Mais puisque je ne suis pas une sorte de développeur normal, mon Object
a normal properties
, circular structures
et même nested objects
.
alors créons d'abord un circular structure
et un nested object
.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
rassemblons tout dans un Object
nommé a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
ensuite, nous voulons copier a
dans une variable appelée b
et la muter.
var b = a;
b.x = 'b';
b.nested.y = 'b';
vous savez ce qui s'est passé ici parce que sinon vous n'atterririez même pas sur cette grande question.
console.log(a, b);
a --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
trouvons une solution.
JSON
la première tentative que j'ai essayé était d'utiliser JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
ne perdez pas trop de temps, vous aurez TypeError: Converting circular structure to JSON
.
copie Récursive (accepté la "réponse"),
regardons la réponse acceptée.
function cloneSO(obj) {
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = cloneSO(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
var copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
C'est bien, hein? C'est une copie récursive de l'objet et traite d'autres types aussi bien, comme Date
, mais ce n'était pas une exigence.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
récursion et circular structures
ne fonctionne pas bien ensemble... RangeError: Maximum call stack size exceeded
solution native
après avoir discuté avec mon collègue, mon patron nous a demandé ce qui s'était passé, et il a trouvé une simple solution après certains googler. Ça s'appelle Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
cette solution a été ajoutée au Javascript il y a quelque temps et traite même circular structure
.
console.log(a, b);
a --> Object {
x: "a",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
... et vous voyez, ça n'a pas marché avec la structure emboîtée à l'intérieur.
polyfill pour la solution native
il y a un polyfill pour Object.create
dans l'ancien navigateur tout comme le IE 8. C'est quelque chose comme recommandé par Mozilla, et de bien sûr, ce n'est pas parfait et il en résulte le même problème que le solution native .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
j'ai mis F
en dehors de la portée afin que nous puissions avoir un regard sur ce que instanceof
nous dit.
console.log(a, b);
a --> Object {
x: "a",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> F {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
console.log(typeof a, typeof b);
a --> object
b --> object
console.log(a instanceof Object, b instanceof Object);
a --> true
b --> true
console.log(a instanceof F, b instanceof F);
a --> false
b --> true
même problème que le solution native , mais un peu pire sortie.
le mieux (mais pas parfait) solution
quand en fouillant, j'ai trouvé une question similaire ( en Javascript, lors de l'exécution d'une copie profonde, Comment éviter un cycle, en raison d'une propriété étant "ceci"? ) à celui-ci, mais avec une bien meilleure solution.
function cloneDR(o) {
const gdcc = "__getDeepCircularCopy__";
if (o !== Object(o)) {
return o; // primitive value
}
var set = gdcc in o,
cache = o[gdcc],
result;
if (set && typeof cache == "function") {
return cache();
}
// else
o[gdcc] = function() { return result; }; // overwrite
if (o instanceof Array) {
result = [];
for (var i=0; i<o.length; i++) {
result[i] = cloneDR(o[i]);
}
} else {
result = {};
for (var prop in o)
if (prop != gdcc)
result[prop] = cloneDR(o[prop]);
else if (set)
result[prop] = cloneDR(cache);
}
if (set) {
o[gdcc] = cache; // reset
} else {
delete o[gdcc]; // unset again
}
return result;
}
var b = cloneDR(a);
b.x = 'b';
b.nested.y = 'b';
et regardons la sortie...
console.log(a, b);
a --> Object {
x: "a",
circ: Object {
me: Object { ... }
},
nested: Object {
y: "a"
}
}
b --> Object {
x: "b",
circ: Object {
me: Object { ... }
},
nested: Object {
y: "b"
}
}
console.log(typeof a, typeof b);
a --> object
b --> object
console.log(a instanceof Object, b instanceof Object);
a --> true
b --> true
console.log(a instanceof F, b instanceof F);
a --> false
b --> false
les exigences sont satisfaites, mais il y a encore des problèmes plus petits, y compris le changement du instance
de nested
et circ
à Object
.
la structure des arbres qui partagent une feuille ne sera pas copiée, ils deviendront deux feuilles indépendantes:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
conclusion
la dernière solution utilisant la récursion et une cache, n'est peut-être pas la meilleure, mais c'est une réelle copie profonde de l'objet. Il gère simple properties
, circular structures
et nested object
, mais il va exemple, alors que le clonage.
une solution particulièrement inélégante est d'utiliser l'encodage JSON pour faire des copies profondes d'objets qui n'ont pas de méthodes membres. La méthodologie consiste à encoder JSON votre objet cible, puis en le décodant, vous obtenez la copie que vous recherchez. Vous pouvez décoder autant de fois que vous voulez pour faire autant de copies que vous avez besoin.
bien sûr, les fonctions n'ont pas leur place dans JSON, donc cela ne fonctionne que pour les objets sans méthode de membre.
cette méthodologie était parfait pour mon cas d'utilisation, puisque je stocke des blobs JSON dans un magasin de valeurs clés, et quand ils sont exposés comme des objets dans une API JavaScript, chaque objet contient en fait une copie de l'état original de l'objet de sorte que nous pouvons calculer le delta après que l'appelant a muté l'objet exposé.
var object1 = {key:"value"};
var object2 = object1;
object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);
object2.key = "a change";
console.log(object1);// returns value
, Vous pouvez simplement utiliser un propagation de la propriété pour copier un objet sans références. Mais attention (voir Commentaires), la' copie ' est juste au plus bas niveau objet/tableau. Les propriétés imbriquées sont toujours des références!
clone complet:
let x = {a: 'value1'}
let x2 = {...x}
// => mutate without references:
x2.a = 'value2'
console.log(x.a) // => 'value1'
Clone avec références au second niveau:
const y = {a: {b: 'value3'}}
const y2 = {...y}
// => nested object is still a references:
y2.a.b = 'value4'
console.log(y.a.b) // => 'value4'
JavaScript ne supporte pas les clones profonds nativement. Utilisez une fonction utilitaire. Par exemple Ramda:
pour ceux qui utilisent AngularJS, il existe également une méthode directe pour cloner ou étendre les objets de cette bibliothèque.
var destination = angular.copy(source);
ou
angular.copy(source, destination);
plus en angle.copie documentation ...
OK, imaginez que vous ayez cet objet ci-dessous et que vous voulez le cloner:
let obj = {a:1, b:2, c:3}; //ES6
ou
var obj = {a:1, b:2, c:3}; //ES5
la réponse est principalement depeneds sur lequel ECMAscript vous en utilisant, dans ES6+
, vous pouvez simplement utiliser Object.assign
pour faire le clone:
let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};
ou en utilisant l'opérateur spread comme ceci:
let cloned = {...obj}; //new {a:1, b:2, c:3};
Mais si vous utilisez ES5
, vous pouvez utiliser peu de méthodes, mais le JSON.stringify
, assurez-vous juste que vous n'utilisez pas pour un gros morceau de données à copier, mais il pourrait être une ligne maniable dans de nombreux cas, quelque chose comme ceci:
let cloned = JSON.parse(JSON.stringify(obj));
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over
A. Levy réponse est presque terminée, voici ma petite contribution: il y a une façon de gérer récursive références , voir cette ligne
if(this[attr]==this) copy[attr] = copy;
si l'objet est un élément DOM XML, nous devons utiliser cloneNode à la place
if(this.cloneNode) return this.cloneNode(true);
inspiré par L'étude exhaustive D'A. Levy et L'approche de Calvin en prototypage, je propose cette solution:
Object.prototype.clone = function() {
if(this.cloneNode) return this.cloneNode(true);
var copy = this instanceof Array ? [] : {};
for(var attr in this) {
if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
copy[attr] = this[attr];
else if(this[attr]==this) copy[attr] = copy;
else copy[attr] = this[attr].clone();
}
return copy;
}
Date.prototype.clone = function() {
var copy = new Date();
copy.setTime(this.getTime());
return copy;
}
Number.prototype.clone =
Boolean.prototype.clone =
String.prototype.clone = function() {
return this;
}
Voir aussi Andy Burke note les réponses.
à Partir de cet article: Comment copier des tableaux et des objets en Javascript par Brian Huisman:
Object.prototype.clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (var i in this) {
if (i == 'clone') continue;
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
} else newObj[i] = this[i]
} return newObj;
};
dans ES-6 Vous pouvez simplement utiliser L'objet.assigner.(..). Ex:
let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);
Une bonne référence est ici: https://googlechrome.github.io/samples/object-assign-es6 /
Voici une fonction que vous pouvez utiliser.
function clone(obj) {
if(obj == null || typeof(obj) != 'object')
return obj;
var temp = new obj.constructor();
for(var key in obj)
temp[key] = clone(obj[key]);
return temp;
}
vous pouvez cloner un objet et supprimer toute référence du précédent en utilisant une seule ligne de code. Il suffit de faire:
var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references
obj2.text = 'moo2'; // Only updates obj2's text property
console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}
pour les navigateurs / moteurs qui ne prennent pas actuellement en charge L'objet.vous pouvez utiliser ce polyfill:
// Polyfill Object.create if it does not exist
if (!Object.create) {
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
Nouvelle réponse à une vieille question! Si vous avez le plaisir d'utiliser ECMAScript 2016 (ES6) avec "Spread Syntax , c'est facile.
keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}
cela fournit une méthode propre pour une copie superficielle d'un objet. Faire une copie profonde, c'est-à-dire faire une nouvelle copie de chaque valeur dans chaque objet imbriqué de façon récursive, nécessite l'une des solutions plus lourdes ci-dessus.
JavaScript continue d'évoluer.
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)
solution ES6 si vous voulez (superficiel) cloner un instance de classe et pas seulement un objet de propriété.
Intéressés par le clonage des objets simples :
JSON.parse (JSON.stringify (json_original));
Source: comment copier objet JavaScript vers une nouvelle variable non par référence?
la réponse de Jan Turoň ci-dessus est très proche, et peut-être la meilleure à utiliser dans un navigateur en raison de problèmes de compatibilité, mais cela pourrait causer quelques étranges problèmes d'énumération. Par exemple, l'exécution:
for ( var i in someArray ) { ... }
assignera la méthode clone() à i après itération à travers les éléments du tableau. Voici une adaptation qui évite l'énumération et fonctionne avec noeud.js:
Object.defineProperty( Object.prototype, "clone", {
value: function() {
if ( this.cloneNode )
{
return this.cloneNode( true );
}
var copy = this instanceof Array ? [] : {};
for( var attr in this )
{
if ( typeof this[ attr ] == "function" || this[ attr ] == null || !this[ attr ].clone )
{
copy[ attr ] = this[ attr ];
}
else if ( this[ attr ] == this )
{
copy[ attr ] = copy;
}
else
{
copy[ attr ] = this[ attr ].clone();
}
}
return copy;
}
});
Object.defineProperty( Date.prototype, "clone", {
value: function() {
var copy = new Date();
copy.setTime( this.getTime() );
return copy;
}
});
Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );
cela évite de faire la méthode clone() énumérable parce que defineProperty () par défaut énumérable à false.
il s'agit d'une adaptation du code de A. Levy pour gérer également le clonage de fonctions et de références multiples/cycliques - ce qui signifie que si deux propriétés dans l'arbre qui est cloné sont des références du même objet, l'arbre d'objet cloné aura ces propriétés pointer vers un seul et même clone de l'objet référencé. Cela résout également le cas des dépendances cycliques qui, si elles ne sont pas manipulées, conduisent à une boucle infinie. La complexité de l'algorithme est O (n)
function clone(obj){
var clonedObjectsArray = [];
var originalObjectsArray = []; //used to remove the unique ids when finished
var next_objid = 0;
function objectId(obj) {
if (obj == null) return null;
if (obj.__obj_id == undefined){
obj.__obj_id = next_objid++;
originalObjectsArray[obj.__obj_id] = obj;
}
return obj.__obj_id;
}
function cloneRecursive(obj) {
if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;
// Handle Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0; i < obj.length; ++i) {
copy[i] = cloneRecursive(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
if (clonedObjectsArray[objectId(obj)] != undefined)
return clonedObjectsArray[objectId(obj)];
var copy;
if (obj instanceof Function)//Handle Function
copy = function(){return obj.apply(this, arguments);};
else
copy = {};
clonedObjectsArray[objectId(obj)] = copy;
for (var attr in obj)
if (attr != "__obj_id" && obj.hasOwnProperty(attr))
copy[attr] = cloneRecursive(obj[attr]);
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
var cloneObj = cloneRecursive(obj);
//remove the unique ids
for (var i = 0; i < originalObjectsArray.length; i++)
{
delete originalObjectsArray[i].__obj_id;
};
return cloneObj;
}
Quelques tests rapides
var auxobj = {
prop1 : "prop1 aux val",
prop2 : ["prop2 item1", "prop2 item2"]
};
var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;
obj.f1 = function (){
this.prop1 = "prop1 val changed by f1";
};
objclone = clone(obj);
//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));
objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));
objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
j'ai écrit ma propre implémentation. Pas sûr si elle compte comme une meilleure solution:
/*
a function for deep cloning objects that contains other nested objects and circular structures.
objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
index (z)
|
|
|
|
|
| depth (x)
|_ _ _ _ _ _ _ _ _ _ _ _
/_/_/_/_/_/_/_/_/_/
/_/_/_/_/_/_/_/_/_/
/_/_/_/_/_/_/...../
/................./
/..... /
/ /
/------------------
object length (y) /
*/
suit la mise en œuvre:
function deepClone(obj) {
var depth = -1;
var arr = [];
return clone(obj, arr, depth);
}
/**
*
* @param obj source object
* @param arr 3D array to store the references to objects
* @param depth depth of the current object relative to the passed 'obj'
* @returns {*}
*/
function clone(obj, arr, depth){
if (typeof obj !== "object") {
return obj;
}
var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'
var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
if(result instanceof Array){
result.length = length;
}
depth++; // depth is increased because we entered an object here
arr[depth] = []; // this is the x-axis, each index here is the depth
arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
// start the depth at current and go down, cyclic structures won't form on depths more than the current one
for(var x = depth; x >= 0; x--){
// loop only if the array at this depth and length already have elements
if(arr[x][length]){
for(var index = 0; index < arr[x][length].length; index++){
if(obj === arr[x][length][index]){
return obj;
}
}
}
}
arr[depth][length].push(obj); // store the object in the array at the current depth and length
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
}
return result;
}
je voulais juste ajouter à toutes les solutions Object.create
dans ce post, que cela ne fonctionne pas de la manière souhaitée avec nodejs.
dans Firefox le résultat de
var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´
est
{test:"test"}
.
En nodejs c'est
{}
function clone(src, deep) {
var toString = Object.prototype.toString;
if(!src && typeof src != "object"){
//any non-object ( Boolean, String, Number ), null, undefined, NaN
return src;
}
//Honor native/custom clone methods
if(src.clone && toString.call(src.clone) == "[object Function]"){
return src.clone(deep);
}
//DOM Elements
if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
return src.cloneNode(deep);
}
//Date
if(toString.call(src) == "[object Date]"){
return new Date(src.getTime());
}
//RegExp
if(toString.call(src) == "[object RegExp]"){
return new RegExp(src);
}
//Function
if(toString.call(src) == "[object Function]"){
//Wrap in another method to make sure == is not true;
//Note: Huge performance issue due to closures, comment this :)
return (function(){
src.apply(this, arguments);
});
}
var ret, index;
//Array
if(toString.call(src) == "[object Array]"){
//[].slice(0) would soft clone
ret = src.slice();
if(deep){
index = ret.length;
while(index--){
ret[index] = clone(ret[index], true);
}
}
}
//Object
else {
ret = src.constructor ? new src.constructor() : {};
for (var prop in src) {
ret[prop] = deep
? clone(src[prop], true)
: src[prop];
}
}
return ret;
};
puisque mindeavor a déclaré que l'objet à cloner est un objet "construit littéralement", une solution pourrait être de simplement générer l'objet plusieurs fois plutôt que de cloner une instance de l'objet:
function createMyObject()
{
var myObject =
{
...
};
return myObject;
}
var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
Consultez http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data pour le W3C sécurisé de transmission de données structurées" de l'algorithme, destiné à être mis en œuvre par les navigateurs de la transmission des données par exemple à des web workers. Cependant, il a quelques limites, en ce qu'il ne gère pas les fonctions. Voir https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm pour plus d'informations, y compris un un algorithme alternatif dans JS qui vous permet d'y aller en partie.
ci-dessous est ma version du clonage profond, des fonctions de couverture et avec manipulation pour les références circulaires.