มีปัญหาหลายอย่างกับการแก้ปัญหาส่วนใหญ่บนอินเทอร์เน็ต ดังนั้นฉันจึงตัดสินใจติดตามซึ่งรวมถึงสาเหตุที่คำตอบที่ยอมรับไม่ควรได้รับการยอมรับ
สถานการณ์เริ่มต้น
ฉันต้องการคัดลอกจาวาสคริปต์ที่Object
มีลูก ๆ ของมันและลูก ๆ ของมันเป็นต้น แต่เนื่องจากผมไม่ได้ชนิดของนักพัฒนาปกติของฉันObject
มีปกติ properties
, และแม้กระทั่งcircular structures
nested objects
ดังนั้นเรามาสร้างcircular structure
และ a nested object
ก่อน
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Let 's นำทุกอย่างเข้าด้วยกันในชื่อ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
วิธีแก้ปัญหาพื้นเมือง
หลังจากถกเถียงกับเพื่อนร่วมงานของฉันเจ้านายของฉันถามเราว่าเกิดอะไรขึ้นและเขาพบวิธีแก้ปัญหาง่ายๆหลังจาก googling 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"
}
}
... และคุณจะเห็นว่ามันไม่ได้ทำงานกับโครงสร้างที่ซ้อนอยู่ภายใน
polyfill สำหรับสารละลายดั้งเดิม
มี polyfill สำหรับ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