Istnieje wiele problemów z większością rozwiązań w Internecie. Postanowiłem więc podjąć dalsze działania, w tym wyjaśnić, dlaczego zaakceptowana odpowiedź nie powinna zostać zaakceptowana.
sytuacja początkowa
Chcę głęboko skopiować Javascript Object
ze wszystkimi jego dziećmi i ich dziećmi i tak dalej. Ale ponieważ nie jestem normalnym programistą, mój Object
ma normalny properties
, circular structures
a nawetnested objects
.
Stwórzmy więc circular structure
a a nested object
first.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Połączmy wszystko w jednym Object
nazwanym a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Następnie chcemy skopiować a
do zmiennej o nazwie b
i zmutować ją.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Wiesz, co się tutaj wydarzyło, bo jeśli nie, nie trafiłbyś nawet na to wspaniałe pytanie.
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"
}
}
Teraz znajdźmy rozwiązanie.
JSON
Pierwszą próbą, której próbowałem, było użycie JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Nie marnuj na to zbyt wiele czasu, dostaniesz TypeError: Converting circular structure to JSON
.
Kopiowanie rekurencyjne (zaakceptowana „odpowiedź”)
Rzućmy okiem na przyjętą odpowiedź.
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.");
}
Wygląda dobrze, heh? Jest to rekurencyjna kopia obiektu i obsługuje również inne typy Date
, ale nie było to wymagane.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Rekurencja i circular structures
nie działa dobrze razem ...RangeError: Maximum call stack size exceeded
natywne rozwiązanie
Po kłótni z moim współpracownikiem mój szef zapytał nas, co się stało, a po pewnym czasie google znalazł proste rozwiązanie . To się nazywa Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
To rozwiązanie zostało dodane do Javascript jakiś czas temu, a nawet obsługuje 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"
}
}
... i widzisz, to nie działało z zagnieżdżoną strukturą wewnątrz.
polyfill do natywnego rozwiązania
W Object.create
starszej przeglądarce, podobnie jak IE 8., znajduje się polifill . Jest to coś takiego, jak zalecane przez Mozillę i oczywiście nie jest idealne i powoduje ten sam problem co rozwiązanie natywne .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Wyłączyłem F
zakres, abyśmy mogli zobaczyć, co instanceof
nam mówi.
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
Ten sam problem co rozwiązanie natywne , ale nieco gorszy wynik.
lepsze (ale nie idealne) rozwiązanie
Podczas kopania znalazłem podobne pytanie (w JavaScript, kiedy wykonuję głęboką kopię, jak mogę uniknąć cyklu, ponieważ właściwość jest „tym”? ) Do tego, ale ze znacznie lepszym rozwiązaniem.
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';
I spójrzmy na wynik ...
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
Wymagania są dopasowane, ale nadal istnieją pewne kwestie, w tym mniejsze zmieniając instance
od nested
i circ
do Object
.
Struktura drzew dzielących liść nie zostanie skopiowana, staną się dwoma niezależnymi liśćmi:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
wniosek
Ostatnie rozwiązanie wykorzystujące rekurencję i pamięć podręczną może nie być najlepsze, ale jest to prawdziwa głęboka kopia obiektu. Zajmuje proste properties
, circular structures
a nested object
, ale to będzie bałagan instancji z nich podczas klonowania.
jsfiddle