Existem vários problemas com a maioria das soluções na internet. Então, decidi fazer um acompanhamento, que inclui por que a resposta aceita não deveria ser aceita.
situação inicial
Quero copiar em profundidade um Javascript Object
com todos os seus filhos e seus filhos e assim por diante. Mas desde que eu não sou um tipo de desenvolvedor normal, a minha Object
tem normais properties
, circular structures
e mesmo nested objects
.
Então, vamos criar um circular structure
e um nested object
primeiro.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Vamos reunir tudo em um Object
nome a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Em seguida, queremos copiar a
para uma variável chamada b
e modificá-la.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Você sabe o que aconteceu aqui, porque senão você nem chegaria a essa grande questão.
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"
}
}
Agora vamos encontrar uma solução.
JSON
A primeira tentativa que tentei foi usar JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Não perca muito tempo com isso, você receberá TypeError: Converting circular structure to JSON
.
Cópia recursiva (a "resposta" aceita)
Vamos dar uma olhada na resposta aceita.
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.");
}
Parece bom, hein? É uma cópia recursiva do objeto e também lida com outros tipos Date
, mas isso não era um requisito.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Recursão e circular structures
não funciona bem em conjunto ...RangeError: Maximum call stack size exceeded
solução nativa
Depois de discutir com meu colega de trabalho, meu chefe nos perguntou o que havia acontecido e ele encontrou uma solução simples depois de pesquisar no Google. É chamado Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Esta solução foi adicionada ao Javascript há algum tempo e até lida com 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 veja bem, não funcionou com a estrutura aninhada dentro.
polyfill para a solução nativa
Existe um polyfill Object.create
no navegador mais antigo, assim como o IE 8. É algo como recomendado pelo Mozilla e, é claro, não é perfeito e resulta no mesmo problema da solução nativa .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Eu coloquei F
fora do escopo para que possamos dar uma olhada no que instanceof
nos diz.
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
Mesmo problema que a solução nativa , mas saída um pouco pior.
a melhor solução (mas não perfeita)
Ao pesquisar, encontrei uma pergunta semelhante ( em Javascript, ao executar uma cópia em profundidade, como evito um ciclo, devido a uma propriedade ser "isto"? ), Mas com uma solução muito melhor.
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 vamos dar uma olhada na saída ...
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
Os requisitos são atendidos, mas ainda existem alguns problemas menores, incluindo a alteração instance
de nested
e circ
para Object
.
A estrutura das árvores que compartilham uma folha não será copiada, elas se tornarão duas folhas independentes:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
conclusão
A última solução usando recursão e um cache, pode não ser o melhor, mas é um verdadeiro deep-cópia do objeto. Ele lida com simples properties
, circular structures
e nested object
, mas vai atrapalhar a instância deles durante a clonagem.
jsfiddle