Esistono diversi problemi con la maggior parte delle soluzioni su Internet. Quindi ho deciso di fare un follow-up, che include, perché la risposta accettata non dovrebbe essere accettata.
situazione iniziale
Voglio copiare in profondità un Javascript Objectcon tutti i suoi figli, i loro figli e così via. Ma dal momento che io non sono tipo di uno sviluppatore normale, il mio Objectha normale properties , circular structurese anche nested objects.
Quindi creiamo un circular structuree un nested objectprimo.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Riuniamo tutto in un Objectnome a.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Successivamente, vogliamo copiare ain una variabile denominata be mutarla.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Sai cosa è successo qui perché altrimenti non ti atterresti nemmeno su questa grande domanda.
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"
}
}
Ora troviamo una soluzione.
JSON
Il primo tentativo che ho provato è stato usare JSON.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Non perdere troppo tempo, otterrai TypeError: Converting circular structure to JSON.
Copia ricorsiva (la "risposta" accettata)
Diamo un'occhiata alla risposta accettata.
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.");
}
Sembra buono, eh? È una copia ricorsiva dell'oggetto e gestisce anche altri tipi Date, ma non era un requisito.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Ricorsione e circular structuresnon funziona bene insieme ...RangeError: Maximum call stack size exceeded
soluzione nativa
Dopo aver litigato con il mio collega, il mio capo ci ha chiesto cosa fosse successo e ha trovato una soluzione semplice dopo aver cercato su Google. Si chiama Object.create.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Questa soluzione è stata aggiunta a Javascript qualche tempo fa e persino gestisce 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"
}
}
... e vedi, non ha funzionato con la struttura nidificata all'interno.
polyfill per la soluzione nativa
Nel Object.createbrowser più vecchio c'è un polyfill proprio come IE 8. È qualcosa come raccomandato da Mozilla e, naturalmente, non è perfetto e comporta lo stesso problema della soluzione nativa .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Ho messo Fal di fuori del campo di applicazione in modo che possiamo dare un'occhiata a ciò che instanceofci dice.
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
Stesso problema della soluzione nativa , ma output leggermente peggiore.
la soluzione migliore (ma non perfetta)
Quando ho cercato, ho trovato una domanda simile ( in Javascript, quando eseguo una copia profonda, come posso evitare un ciclo, a causa di una proprietà "questo"? ) A questo, ma con una soluzione migliore.
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';
E diamo un'occhiata all'output ...
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
I requisiti sono abbinati, ma ci sono ancora alcuni problemi minori, tra cui il cambiamento instancedi nestede circa Object.
La struttura degli alberi che condividono una foglia non verrà copiata, diventeranno due foglie indipendenti:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
conclusione
L'ultima soluzione che utilizza la ricorsione e una cache, potrebbe non essere la migliore, ma è una vera copia profonda dell'oggetto. Gestisce semplice properties, circular structurese nested object, ma sarà rovinare l'istanza di loro, mentre la clonazione.
jsfiddle