Il existe plusieurs problèmes avec la plupart des solutions sur Internet. J'ai donc 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 copier en profondeur un Javascript Object
avec tous ses enfants et leurs enfants et ainsi de suite. Mais puisque je ne suis pas une sorte de développeur normale, mon Object
a la normale properties
, circular structures
et même nested objects
.
Créons donc un circular structure
et un nested object
premier.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Rassemblons tout dans un Object
nom a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Ensuite, nous voulons copier a
dans une variable nommé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"
}
}
Maintenant, trouvons une solution.
JSON
La première tentative que j'ai essayée a été d'utiliser JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Ne perdez pas trop de temps dessus, vous en aurez TypeError: Converting circular structure to JSON
.
Copie récursive (la "réponse" acceptée)
Jetons un œil à 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.");
}
Ça a l'air bien, hein? C'est une copie récursive de l'objet et gère également d'autres types Date
, mais ce n'était pas une exigence.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Récursivité 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 solution simple après quelques recherches sur Google. Ça s'appelle Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Cette solution a été ajoutée à Javascript il y a quelque temps et gère 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, cela ne fonctionnait pas avec la structure imbriquée à l'intérieur.
polyfill pour la solution native
Il y a un polyfill pour Object.create
l'ancien navigateur, tout comme IE 8. C'est quelque chose comme recommandé par Mozilla, et bien sûr, ce n'est pas parfait et entraîne le même problème que la 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 en F
dehors du champ d'application afin que nous puissions voir ce qui 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 la solution native , mais une sortie un peu moins bonne.
la meilleure solution (mais pas parfaite)
En fouillant, j'ai trouvé une question similaire ( en Javascript, lors de l'exécution d'une copie complète , comment puis-je éviter un cycle, car une propriété est "this"? ) À celle-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 identiques, mais il y a encore quelques petits problèmes, y compris la modification instance
du nested
et circ
du 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écursivité et un cache n'est peut-être pas la meilleure, mais c'est une véritable copie en profondeur de l'objet. Il gère simple properties
, circular structures
et nested object
, mais il gâchera leur instance lors du clonage.
jsfiddle