В Інтернеті є декілька проблем з більшістю рішень. Тож я вирішив здійснити подальшу діяльність, яка включає, чому прийняту відповідь не слід приймати.
вихідна ситуація
Я хочу копіювати 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
рідне рішення
Сперечавшись із моїм колегою, мій бос запитав нас, що сталося, і він знайшов просте рішення після деякого гугла. Це називається 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 при виконанні глибокої копії, як я уникаю циклу через те, що властивість "це"? ) До цього, але з кращим рішенням.
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
положення nested
та circ
на Object
.
Структура дерев, які діляться листям, не буде скопійована, вони стануть двома незалежними листками:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
висновок
Останнє рішення з використанням рекурсії та кешу, можливо, не найкраще, але це справжня глибока копія об'єкта. Він обробляє простий properties
, circular structures
і nested object
, але це зіпсує екземпляр з них при клонуванні.
jsfiddle