Có một số vấn đề với hầu hết các giải pháp trên internet. Vì vậy, tôi quyết định thực hiện theo dõi, bao gồm, tại sao câu trả lời được chấp nhận không nên được chấp nhận.
tình huống bắt đầu
Tôi muốn sao chép sâu một Javascript Object
với tất cả trẻ em và con của chúng, v.v. Nhưng vì tôi không loại một nhà phát triển bình thường, tôi Object
có bình thường properties
, circular structures
và thậm chí cả nested objects
.
Vì vậy, hãy tạo một circular structure
và nested object
đầu tiên.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Chúng ta hãy mang mọi thứ lại với nhau trong một Object
cái tên a
.
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Tiếp theo, chúng tôi muốn sao chép a
vào một biến có tên b
và biến đổi nó.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Bạn biết những gì đã xảy ra ở đây bởi vì nếu không bạn thậm chí sẽ không đáp ứng câu hỏi tuyệt vời này.
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"
}
}
Bây giờ hãy tìm một giải pháp.
JSON
Nỗ lực đầu tiên tôi đã thử là sử dụng JSON
.
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Đừng lãng phí quá nhiều thời gian cho nó, bạn sẽ nhận được TypeError: Converting circular structure to JSON
.
Bản sao đệ quy ("câu trả lời" được chấp nhận)
Chúng ta hãy xem câu trả lời được chấp nhận.
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.");
}
Có vẻ tốt, heh? Đó là một bản sao đệ quy của đối tượng và xử lý các loại khác, như Date
, nhưng đó không phải là một yêu cầu.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Đệ quy và circular structures
không làm việc tốt với nhau ...RangeError: Maximum call stack size exceeded
giải pháp bản địa
Sau khi tranh cãi với đồng nghiệp của tôi, sếp của tôi đã hỏi chúng tôi chuyện gì đã xảy ra và anh ta đã tìm ra một giải pháp đơn giản sau một vài cuộc cãi vã . Nó được gọi là Object.create
.
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Giải pháp này đã được thêm vào Javascript một thời gian trước và thậm chí xử lý 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"
}
}
... Và bạn thấy đấy, nó không hoạt động với cấu trúc lồng nhau bên trong.
polyfill cho giải pháp bản địa
Có một polyfill dành cho Object.create
trình duyệt cũ hơn như IE 8. Nó giống như được khuyến nghị bởi Mozilla, và tất nhiên, nó không hoàn hảo và dẫn đến vấn đề tương tự như giải pháp gốc .
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Tôi đã đặt F
ngoài phạm vi để chúng tôi có thể xem những gì instanceof
cho chúng tôi biết.
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
Vấn đề tương tự như giải pháp tự nhiên , nhưng đầu ra tồi tệ hơn một chút.
giải pháp tốt hơn (nhưng không hoàn hảo)
Khi đào xung quanh, tôi đã tìm thấy một câu hỏi tương tự ( Trong Javascript, khi thực hiện một bản sao sâu, làm cách nào để tránh một chu kỳ, do một thuộc tính là "cái này"? ) Cho câu hỏi này, nhưng với một giải pháp tốt hơn.
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';
Và hãy nhìn vào đầu ra ...
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
Các yêu cầu được kết hợp, nhưng vẫn còn một số vấn đề nhỏ hơn, bao gồm cả việc thay đổi instance
của nested
và circ
đến Object
.
Cấu trúc của những cây chia sẻ một chiếc lá sẽ không được sao chép, chúng sẽ trở thành hai chiếc lá độc lập:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
phần kết luận
Giải pháp cuối cùng sử dụng đệ quy và bộ đệm, có thể không phải là tốt nhất, nhưng đó là một bản sao sâu thực sự của đối tượng. Nó xử lý đơn giản properties
, circular structures
và nested object
, nhưng nó sẽ gây rối cho trường hợp của chúng trong khi nhân bản.
jsfiddle