Есть несколько проблем с большинством решений в Интернете. Поэтому я решил сделать продолжение, которое включает, почему принятый ответ не должен быть принят.
стартовая ситуация
я бы хотел глубоко скопировать Javascript Object
со всеми его детьми и их детьми и так далее. Но так как я не нормальный разработчик, у меня Object
есть нормальный properties
, circular structures
и даже nested objects
.
Итак, давайте создадим circular structure
и nested object
первый.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Давайте соберем все вместе по Object
имени a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Далее мы хотим скопировать a
в переменную с именем b
и изменить ее.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Вы знаете, что здесь произошло, потому что если бы не вы, вы бы даже не попали на этот великий вопрос.
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"
}
}
Теперь давайте найдем решение.
JSON
Первая попытка, которую я попробовал, использовала JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Не тратьте слишком много времени на это, вы получите TypeError: Converting circular structure to JSON
.
Рекурсивная копия (принятый «ответ»)
Давайте посмотрим на принятый ответ.
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.");
}
Выглядит хорошо, а? Это рекурсивная копия объекта, которая также обрабатывает другие типы Date
, но это не было обязательным требованием.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Рекурсия и circular structures
не очень хорошо работают вместе ...RangeError: Maximum call stack size exceeded
нативное решение
После спора с моим коллегой, мой начальник спросил нас, что случилось, и он нашел простое решение после некоторого поиска в Google. Это называется Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Это решение было добавлено в Javascript некоторое время назад и даже обрабатывает 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"
}
}
... и вы видите, это не сработало с вложенной структурой внутри.
полифилл для нативного раствора
В Object.create
старом браузере, как и в IE 8 , есть полифилл. Это что-то вроде рекомендованного Mozilla, и, конечно, оно не идеально и приводит к той же проблеме, что и нативное решение .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Я F
вышел за рамки, чтобы мы могли взглянуть на то, что instanceof
говорит нам.
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
Та же проблема, что и у нативного решения , но немного худший результат.
лучшее (но не идеальное) решение
Копаясь, я нашел похожий вопрос ( в Javascript, когда я выполняю глубокое копирование, как мне избежать цикла из-за свойства «this»? ) На этот, но с более лучшим решением.
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';
И давайте посмотрим на вывод ...
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
Требования соответствуют, но все еще есть некоторые меньшие проблемы, включая изменение instance
of nested
и circ
to Object
.
Структура деревьев, имеющих общий лист, не будет скопирована, они станут двумя независимыми листьями:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
вывод
Последнее решение, использующее рекурсию и кеш, может быть не лучшим, но это настоящая глубокая копия объекта. Он обрабатывает простой properties
, circular structures
и nested object
, но это испортит экземпляр из них при клонировании.
jsfiddle