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 Object
con tutti i suoi figli, i loro figli e così via. Ma dal momento che io non sono tipo di uno sviluppatore normale, il mio Object
ha normale properties
, circular structures
e anche nested objects
.
Quindi creiamo un circular structure
e un nested object
primo.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Riuniamo tutto in un Object
nome a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Successivamente, vogliamo copiare a
in una variabile denominata b
e 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 structures
non 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.create
browser 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 F
al di fuori del campo di applicazione in modo che possiamo dare un'occhiata a ciò che instanceof
ci 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 instance
di nested
e circ
a 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 structures
e nested object
, ma sarà rovinare l'istanza di loro, mentre la clonazione.
jsfiddle