Existen varios problemas con la mayoría de las soluciones en Internet. Así que decidí hacer un seguimiento, que incluye, por qué la respuesta aceptada no debería aceptarse.
situación inicial
Quiero copiar en profundidad un Javascript Object
con todos sus hijos y sus hijos, etc. Pero como no soy un desarrollador normal, mi Object
tiene normal properties
, circular structures
e inclusonested objects
.
Así que creemos un circular structure
y un nested object
primero.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Vamos a juntar todo en un Object
nombre a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
A continuación, queremos copiar a
en una variable llamada b
y mutarla.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Sabes lo que pasó aquí porque si no, ni siquiera aterrizarías en esta gran pregunta.
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"
}
}
Ahora encontremos una solución.
JSON
El primer intento que intenté fue usar JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
No pierdas demasiado tiempo en ello, obtendrás TypeError: Converting circular structure to JSON
.
Copia recursiva (la "respuesta" aceptada)
Echemos un vistazo a la respuesta aceptada.
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.");
}
Se ve bien, ¿eh? Es una copia recursiva del objeto y también maneja otros tipos, como Date
, pero eso no era un requisito.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Recursión y circular structures
no funciona bien juntos ...RangeError: Maximum call stack size exceeded
solución nativa
Después de discutir con mi compañero de trabajo, mi jefe nos preguntó qué pasó y encontró una solución simple después de buscar en Google. Se llama Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Esta solución se agregó a Javascript hace algún tiempo e incluso se maneja 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"
}
}
... y ves, no funcionaba con la estructura anidada adentro.
polyfill para la solución nativa
Hay un polyfill para Object.create
el navegador anterior como el IE 8. Es algo como lo recomienda Mozilla, y por supuesto, no es perfecto y da como resultado el mismo problema que la solución nativa .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Lo he puesto F
fuera del alcance para que podamos echar un vistazo a lo que instanceof
nos 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
Mismo problema que la solución nativa , pero un poco peor de salida.
la mejor (pero no perfecta) solución
Al investigar, encontré una pregunta similar ( en Javascript, al realizar una copia profunda, ¿cómo evito un ciclo, debido a que una propiedad es "esto"? ) A esta, pero con una solución mucho mejor.
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';
Y echemos un vistazo a la salida ...
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
Los requisitos coinciden, pero todavía hay algunos problemas menores, incluido el cambio instance
de nested
y circ
hacia Object
.
La estructura de los árboles que comparten una hoja no se copiará, se convertirán en dos hojas independientes:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
conclusión
La última solución que utiliza recursividad y un caché, puede no ser la mejor, pero es una copia profunda real del objeto. Maneja sencilla properties
, circular structures
y nested object
, pero se hace un lío la instancia de ellos durante la clonación.
jsfiddle