ฉันจะโคลนวัตถุ JavaScript ได้อย่างถูกต้องได้อย่างไร


3077

xฉันมีวัตถุ ฉันต้องการที่จะคัดลอกเป็นวัตถุyเช่นว่าการเปลี่ยนแปลงที่จะไม่แก้ไขy xฉันรู้ว่าการคัดลอกวัตถุที่มาจากวัตถุ JavaScript ในตัวจะส่งผลให้มีคุณสมบัติพิเศษที่ไม่ต้องการ นี่ไม่ใช่ปัญหาเนื่องจากฉันคัดลอกหนึ่งในวัตถุที่สร้างขึ้นตามตัวอักษรของฉันเอง

ฉันจะโคลนวัตถุ JavaScript ได้อย่างถูกต้องได้อย่างไร


30
ดูคำถามนี้: stackoverflow.com/questions/122102/…
Niyaz

256
สำหรับ JSON ฉันใช้mObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh

67
ฉันไม่เข้าใจว่าทำไมไม่มีใครแนะนำObject.create(o)มันทำทุกอย่างที่ผู้เขียนถาม?
froginvasion

43
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; หลังจากทำเช่นนี้y.deep.keyจะเป็น 2 ดังนั้น Object.create ไม่สามารถใช้สำหรับการโคลน ...
Ruben Stolk

17
@ r3wt ที่จะไม่ทำงาน ... กรุณาโพสต์เท่านั้นหลังจากทำทดสอบขั้นพื้นฐานของการแก้ปัญหา ..
Akshay

คำตอบ:


1561

การทำเช่นนี้กับวัตถุใด ๆ ใน JavaScript จะไม่ง่ายหรือตรงไปตรงมา คุณจะพบปัญหาในการหยิบคุณสมบัติจากต้นแบบของวัตถุที่ควรจะทิ้งไว้ในต้นแบบและจะไม่ถูกคัดลอกไปยังอินสแตนซ์ใหม่ ตัวอย่างเช่นหากคุณกำลังเพิ่มcloneวิธีการObject.prototypeดังที่คำตอบบางคำอธิบายไว้คุณจะต้องข้ามแอตทริบิวต์นั้นอย่างชัดเจน แต่จะเกิดอะไรขึ้นถ้ามีวิธีการอื่นเพิ่มเติมที่เพิ่มเข้ามาObject.prototypeหรือต้นแบบระดับกลางอื่น ๆ ที่คุณไม่รู้? ในกรณีนี้คุณจะคัดลอกคุณลักษณะที่คุณไม่ควรทำดังนั้นคุณต้องตรวจสอบคุณลักษณะที่ไม่คาดคิดและไม่ใช่ในท้องที่ด้วยhasOwnPropertyวิธีการ

นอกเหนือจากแอตทริบิวต์ที่ไม่สามารถนับได้คุณจะพบปัญหาที่ยากขึ้นเมื่อคุณพยายามคัดลอกวัตถุที่มีคุณสมบัติที่ซ่อนอยู่ ตัวอย่างเช่นprototypeเป็นคุณสมบัติที่ซ่อนของฟังก์ชั่น นอกจากนี้ต้นแบบของวัตถุนั้นถูกอ้างอิงด้วยคุณลักษณะ__proto__ซึ่งถูกซ่อนไว้และจะไม่ถูกคัดลอกโดย for / in วนซ้ำวนซ้ำกับแอตทริบิวต์ของวัตถุต้นฉบับ ฉันคิดว่า__proto__อาจจะเฉพาะเจาะจงกับล่าม JavaScript ของ Firefox และมันอาจเป็นสิ่งที่แตกต่างในเบราว์เซอร์อื่น ๆ แต่คุณได้รับรูปภาพ ไม่ใช่ทุกอย่างที่นับได้ คุณสามารถคัดลอกแอททริบิวที่ซ่อนอยู่หากคุณรู้ชื่อของมัน แต่ฉันไม่รู้วิธีค้นพบมันโดยอัตโนมัติ

อุปสรรคอีกประการหนึ่งในการแสวงหาทางออกที่สง่างามคือปัญหาของการตั้งค่าการสืบทอดต้นแบบอย่างถูกต้อง หากต้นแบบต้นกำเนิดของวัตถุของคุณเป็นObjectเพียงแค่การสร้างวัตถุทั่วไปใหม่ด้วย{}จะทำงาน แต่ถ้าต้นแบบของแหล่งที่มาเป็นลูกหลานของObjectแล้วคุณจะหายไปสมาชิกเพิ่มเติมจากต้นแบบที่คุณข้ามโดยใช้hasOwnPropertyตัวกรองหรือที่ อยู่ในต้นแบบ แต่ไม่ได้นับตั้งแต่แรก วิธีแก้ปัญหาหนึ่งอาจจะเรียกconstructorคุณสมบัติของออบเจกต์ต้นฉบับเพื่อรับออบเจกต์คัดลอกเริ่มต้นแล้วคัดลอกไปที่แอตทริบิวต์ แต่คุณจะยังคงไม่ได้รับคุณสมบัติที่ไม่นับ ตัวอย่างเช่นDateวัตถุเก็บข้อมูลเป็นสมาชิกที่ซ่อนอยู่:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

สตริงวันที่d1จะเป็น 5 d2วินาทีหลังจากนั้น วิธีที่จะทำให้DateเหมือนกันคือการเรียกsetTimeวิธีการ แต่นั่นเป็นวิธีที่เฉพาะเจาะจงกับDateชั้นเรียน ฉันไม่คิดว่าจะมีวิธีแก้ปัญหาทั่วไปที่ใช้กระสุนได้ แต่ฉันก็ยินดีที่จะผิด!

เมื่อผมมีการดำเนินการคัดลอกลึกทั่วไปฉันสิ้นสุดที่สูญเสียโดยสมมติว่าฉันจะต้องเท่านั้นที่จะคัดลอกธรรมดาObject, Array, Date, String, หรือNumber Boolean3 ประเภทสุดท้ายนั้นไม่เปลี่ยนรูปดังนั้นฉันสามารถทำสำเนาตื้น ๆ และไม่ต้องกังวลกับการเปลี่ยนแปลง ฉันคาดเดาเพิ่มเติมว่าองค์ประกอบใด ๆ ที่มีอยู่ในObjectหรือArrayจะเป็นหนึ่งใน 6 ประเภทอย่างง่ายในรายการนั้น สามารถทำได้ด้วยรหัสดังนี้:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

ฟังก์ชั่นด้านบนจะทำงานได้อย่างเพียงพอสำหรับ 6 ประเภทง่าย ๆ ที่ฉันกล่าวถึงตราบเท่าที่ข้อมูลในวัตถุและอาร์เรย์ในรูปแบบโครงสร้างต้นไม้ นั่นคือไม่มีการอ้างอิงมากกว่าหนึ่งไปยังข้อมูลเดียวกันในวัตถุ ตัวอย่างเช่น:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

มันจะไม่สามารถจัดการวัตถุ JavaScript ใด ๆ ได้ แต่อาจเพียงพอสำหรับวัตถุประสงค์มากมายตราบใดที่คุณไม่คิดว่ามันจะใช้ได้กับทุกสิ่งที่คุณทำ


5
เกือบจะทำงานได้ดีใน nodejs - การเพียงแค่มีการเปลี่ยนแปลงบรรทัดสำหรับ (var i = 0, var len = obj.length ฉัน <len; ++ i) {สำหรับ (var i = 0; ฉัน <obj.length; ++ i) {
Trindaz

5
สำหรับผู้ใช้ Google ในอนาคต: สำเนาลึกที่เหมือนกันการส่งการอ้างอิงซ้ำโดยใช้คำสั่ง 'return' ที่gist.github.com/2234277
Trindaz

7
ทุกวันนี้JSON.parse(JSON.stringify([some object]),[some revirer function])จะเป็นทางออกหรือไม่?
KooiInc

5
ในตัวอย่างแรกคุณแน่ใจหรือว่ามันไม่ควรจะเป็นvar cpy = new obj.constructor()?
cyon

8
ดูเหมือนว่าการมอบหมายวัตถุจะไม่ทำสำเนาจริงใน Chrome รักษาการอ้างอิงถึงวัตถุดั้งเดิม - จบลงด้วยการใช้ JSON.stringify และ JSON.parse เพื่อโคลน - ทำงานได้อย่างสมบูรณ์แบบ
1owk3y

974

หากคุณไม่ใช้Dates, ฟังก์ชั่น, ไม่ได้กำหนด, regExp หรืออินฟินิตี้ภายในวัตถุของคุณซับหนึ่งที่ง่ายมากคือJSON.parse(JSON.stringify(object)):

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

ใช้ได้กับวัตถุทุกประเภทที่มีวัตถุอาร์เรย์สตริงบูลีนและตัวเลข

ดูบทความนี้เกี่ยวกับอัลกอริทึมการโคลนโครงสร้างของเบราว์เซอร์ที่ใช้เมื่อโพสต์ข้อความไปยังและจากคนงาน นอกจากนี้ยังมีฟังก์ชันสำหรับการโคลนแบบลึก


42
โปรดทราบว่านี่สามารถใช้สำหรับการทดสอบเท่านั้น ประการแรกมันยังห่างไกลจากความเหมาะสมในแง่ของเวลาและการใช้หน่วยความจำ ประการที่สองเบราว์เซอร์บางตัวไม่มีวิธีการนี้
Nux

2
คุณสามารถรวม JSON2.js หรือ JSON3.js ได้เสมอ คุณจะต้องการมันสำหรับแอพของคุณ แต่ฉันเห็นด้วยว่านี่อาจไม่ใช่ทางออกที่ดีที่สุดเนื่องจาก JSON.stringify ไม่รวมคุณสมบัติที่สืบทอดมา
ทิมหง

78
@Nux ทำไมไม่เหมาะในแง่ของเวลาและหน่วยความจำ MiJyn กล่าวว่า: "สาเหตุที่วิธีการนี้ช้ากว่าการทำสำเนาแบบตื้น (บนวัตถุที่ลึก) คือวิธีการนี้โดยการทำสำเนาแบบลึกโดยนิยาม definition แต่เนื่องจาก JSON ถูกใช้ในโค้ดเนทีฟ (ในเบราว์เซอร์ส่วนใหญ่) กว่าการใช้โซลูชันการทำสำเนาแบบลึกอื่น ๆ ที่ใช้ JavaScript และบางครั้งอาจเร็วกว่าเทคนิคการทำสำเนาแบบตื้นที่ใช้งาน JavaScript (ดู: jsperf.com/cloning-an-object/79) " stackoverflow.com/questions/122102/…
BeauCielBleu

16
ฉันต้องการเพิ่มการอัปเดตนี้ในเดือนตุลาคม 2014 Chrome 37+ เร็วขึ้นด้วย JSON.parse (JSON.stringify (oldObject)); ประโยชน์ของการใช้สิ่งนี้คือมันเป็นเรื่องง่ายมากสำหรับเครื่องมือจาวาสคริปต์ที่จะเห็นและปรับให้เหมาะกับสิ่งที่ดีกว่าถ้ามันต้องการ
mirhagk

17
อัปเดตปี 2559: ตอนนี้ควรใช้กับเบราว์เซอร์ทุกตัวที่ใช้กันอย่างแพร่หลาย (ดูฉันสามารถใช้ ... ) คำถามหลักในขณะนี้คือว่ามันมีประสิทธิภาพเพียงพอหรือไม่
James Foster

783

ด้วย jQuery คุณสามารถทำสำเนาตื้น ๆโดยขยาย :

var copiedObject = jQuery.extend({}, originalObject)

การเปลี่ยนแปลงในภายหลังcopiedObjectจะไม่ส่งผลกระทบต่อoriginalObjectและในทางกลับกัน

หรือเพื่อทำสำเนาลึก :

var copiedObject = jQuery.extend(true, {}, originalObject)

164
หรือแม้กระทั่ง:var copiedObject = jQuery.extend({},originalObject);
แมคลีน

82
นอกจากนี้ยังมีประโยชน์ในการระบุความจริงเป็นพารามิเตอร์แรกสำหรับการทำสำเนาลึก:jQuery.extend(true, {}, originalObject);
Will Shaver

6
ใช่ฉันพบว่าลิงก์นี้มีประโยชน์ (เช่นเดียวกับปาสคาล) stackoverflow.com/questions/122102/…
Garry English

3
เพียงแค่ทราบนี่ไม่ได้คัดลอกตัวสร้างโปรโตของวัตถุดั้งเดิม
Sam Jones

1
ตามstackoverflow.com/questions/184710/...มันก็ดูเหมือนว่า "คัดลอกตื้น" เพียงคัดลอกอ้างอิง originalObject ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa...ดังนั้นทำไมที่นี่บอกว่า ขออภัยที่ฉันสับสนจริงๆ
Carr

686

ใน ECMAScript 6 มีวิธีObject.assignซึ่งจะคัดลอกค่าของคุณสมบัติของตัวเองที่นับได้ทั้งหมดจากวัตถุหนึ่งไปยังอีกวัตถุหนึ่ง ตัวอย่างเช่น:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

แต่โปรดระวังว่าวัตถุที่ซ้อนกันจะยังคงถูกคัดลอกเป็นการอ้างอิง


ใช่ฉันเชื่อว่าObject.assignเป็นวิธีที่จะไป มันง่ายที่จะ polyfill เช่นกัน: gist.github.com/rafaelrinaldi/43813e707970bd2d77fa

22
นอกจากนี้ยังทราบว่านี้จะคัดลอก "วิธีการ" ที่กำหนดไว้ผ่านทางตัวอักษรวัตถุ (ตั้งแต่เหล่านี้มีนับ) แต่ไม่ได้วิธีการท้าทายผ่านทาง "คลาส" กลไก (ตั้งแต่เหล่านี้ไม่ได้นับ)
Marcus Junius Brutus

16
ฉันคิดว่าควรจะกล่าวว่านี่ไม่ใช่ suported โดย IE ยกเว้น Edge บางคนยังคงใช้สิ่งนี้
Saulius

1
นี่เป็นเช่นเดียวกับ @EugeneTiurin คำตอบของเขา
เหี่ยวแห้ง

5
พูดจากประสบการณ์ที่นี่ ... อย่าใช้สิ่งนี้ วัตถุได้รับการออกแบบมาให้เป็นลำดับชั้นและคุณจะคัดลอกในระดับแรกเท่านั้นซึ่งนำคุณไปสู่การกำหนดวัตถุทั้งหมดที่ได้คัดลอก เชื่อฉันสิมันอาจช่วยให้คุณไม่ต้องเกาหลายวัน
ow3n

232

ต่อMDN :

  • หากคุณต้องการคัดลอกตื้นใช้ Object.assign({}, a)
  • สำหรับการทำสำเนา "ลึก" ให้ใช้ JSON.parse(JSON.stringify(a))

ไม่มีความจำเป็นสำหรับห้องสมุดภายนอกไม่เป็น แต่คุณจำเป็นต้องตรวจสอบความเข้ากันได้เบราเซอร์แรก


8
JSON.parse (JSON.stringify (a)) ดูสวยงาม แต่ก่อนใช้ฉันแนะนำให้ทำการเปรียบเทียบเวลาที่ใช้กับคอลเล็กชันที่คุณต้องการ นี่อาจไม่ใช่ตัวเลือกที่เร็วที่สุดทั้งนี้ขึ้นอยู่กับขนาดของวัตถุ
Edza

3
วิธี JSON ฉันสังเกตเห็นการแปลงวัตถุวันที่เพื่อสตริง แต่ไม่กลับไปวันที่ ต้องจัดการกับความสนุกของเขตเวลาใน Javascript และแก้ไขวันที่ด้วยตนเอง อาจเป็นกรณีที่คล้ายกันสำหรับประเภทอื่น ๆ นอกเหนือจากวันที่
Steve Seeger

สำหรับวันที่ฉันจะใช้ moment.js เนื่องจากมีcloneฟังก์ชั่น ดูเพิ่มเติมได้ที่นี่momentjs.com/docs/#/parsing/moment-clone
Tareq

นี่เป็นสิ่งที่ดี แต่ระวังว่าวัตถุนั้นมีฟังก์ชั่นอยู่ข้างใน
lesolorzanov

ปัญหาอื่นที่มีการแยกวิเคราะห์ json คือการอ้างอิงแบบวงกลม หากมีการอ้างอิงแบบวงกลมภายในวัตถุสิ่งนี้จะทำให้แตก
philoj

133

มีคำตอบมากมาย แต่ไม่มีผู้ใดที่กล่าวถึงObject.createจาก ECMAScript 5 ซึ่งเป็นที่ยอมรับไม่ได้ให้สำเนาที่แน่นอน แต่ให้กำหนดแหล่งที่มาให้เป็นต้นแบบของวัตถุใหม่

ดังนั้นนี่ไม่ใช่คำตอบที่ถูกต้องสำหรับคำถาม แต่เป็นวิธีการแก้ปัญหาแบบบรรทัดเดียวและสวยงาม และใช้งานได้ดีที่สุดสำหรับ 2 กรณี:

  1. ในกรณีที่การรับมรดกดังกล่าวมีประโยชน์ (duh!)
  2. ในกรณีที่วัตถุต้นฉบับจะไม่ถูกปรับเปลี่ยนดังนั้นการสร้างความสัมพันธ์ระหว่างวัตถุทั้งสองจึงไม่มีปัญหา

ตัวอย่าง:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

เหตุใดฉันจึงคิดว่าโซลูชันนี้เหนือกว่า มันเป็นแบบพื้นเมืองดังนั้นจึงไม่มีการวนซ้ำไม่มีการเรียกซ้ำ อย่างไรก็ตามเบราว์เซอร์รุ่นเก่าจะต้องใช้ตัวเติม


หมายเหตุ: Object.create ไม่ใช่สำเนาที่ลึก (itpastorn ระบุว่าไม่มีการเรียกซ้ำ) หลักฐาน:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts

103
นี่คือการสืบทอดต้นแบบไม่ใช่การโคลน สิ่งเหล่านี้แตกต่างอย่างสิ้นเชิง วัตถุใหม่ไม่มีคุณสมบัติเป็นของมันเองเพียงแค่ชี้ไปที่คุณสมบัติของต้นแบบ จุดโคลนคือการสร้างวัตถุใหม่ที่ไม่อ้างอิงคุณสมบัติใด ๆ ในวัตถุอื่น
d13

7
ฉันเห็นด้วยกับคุณอย่างสมบูรณ์ ฉันเห็นด้วยเช่นกันว่านี่ไม่ใช่การโคลนนิ่งเท่าที่ควรจะเป็น แต่มากับคนอื่น ๆ ยอมรับธรรมชาติของ JavaScript แทนที่จะพยายามหาทางแก้ปัญหาที่ไม่ได้มาตรฐาน แน่นอนคุณไม่ชอบต้นแบบและพวกเขาทั้งหมด "blah" สำหรับคุณ แต่ในความเป็นจริงมีประโยชน์มากถ้าคุณรู้ว่าคุณกำลังทำอะไร
froginvasion

4
@RobG: บทความนี้จะอธิบายความแตกต่างระหว่างการอ้างอิงและโคลน: en.wikipedia.org/wiki/Cloning_(programming) Object.createชี้ไปที่คุณสมบัติของผู้ปกครองผ่านการอ้างอิงซึ่งหมายความว่าหากค่าคุณสมบัติของผู้ปกครองเปลี่ยนไปเด็กก็จะเปลี่ยนเช่นกัน นี้มีบางส่วนผลข้างเคียงที่น่าแปลกใจกับอาร์เรย์ที่ซ้อนกันและวัตถุที่อาจนำไปสู่ข้อบกพร่องที่ยากต่อการค้นหาในรหัสของคุณถ้าคุณไม่ได้ตระหนักถึงของพวกเขาjsbin.com/EKivInO/2 วัตถุที่โคลนเป็นวัตถุใหม่ที่เป็นอิสระอย่างสมบูรณ์ที่มีคุณสมบัติและค่าเช่นเดียวกับผู้ปกครอง แต่ไม่ได้เชื่อมต่อกับผู้ปกครอง
d13

1
สิ่งนี้ทำให้ผู้คนเข้าใจผิด ... Object.create () สามารถใช้เป็นวิธีการถ่ายทอดทางพันธุกรรม แต่การโคลนไม่ได้อยู่ใกล้กับมัน
prajnavantha

128

วิธีที่สวยงามในการโคลนวัตถุ Javascript ในโค้ดหนึ่งบรรทัด

Object.assignวิธีการเป็นส่วนหนึ่งของ ECMAScript ปี 2015 (ES6) มาตรฐานและไม่ตรงกับสิ่งที่คุณต้องการ

var clone = Object.assign({}, obj);

วิธีการ Object.assign () ใช้ในการคัดลอกค่าของคุณสมบัติของตัวเองที่แจกแจงทั้งหมดจากวัตถุต้นทางอย่างน้อยหนึ่งรายการไปยังวัตถุเป้าหมาย

อ่านเพิ่มเติม...

polyfillเพื่อสนับสนุนเบราว์เซอร์รุ่นเก่า:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

ขออภัยสำหรับคำถามโง่ แต่เหตุใดจึงObject.assignใช้พารามิเตอร์สองตัวเมื่อvalueฟังก์ชันใน polyfill ใช้พารามิเตอร์เดียวเท่านั้น
Qwertie

@Qwertie เมื่อวานนี้ข้อโต้แย้งทั้งหมดจะถูกทำซ้ำและรวมเป็นวัตถุเดียวจัดลำดับความสำคัญของคุณสมบัติจาก ARG ผ่านที่ผ่านมา
ยูจีน Tiurin

โอ้ฉันเห็นแล้วขอบคุณ (ฉันไม่คุ้นเคยกับargumentsวัตถุก่อนหน้านี้) ฉันมีปัญหาในการค้นหาObject()ผ่าน Google ... มันเป็น typecast ใช่ไหม?
Qwertie

44
นี้จะดำเนินการ "โคลน" ตื้นเท่านั้น
Marcus Junius Brutus

1
คำตอบนี้เหมือนกันทั้งหมด: stackoverflow.com/questions/122102/…ฉันรู้ว่ามันเป็นโดยบุคคลเดียวกัน แต่คุณควรอ้างอิงแทนที่จะคัดลอกคำตอบ
lesolorzanov

87

มีปัญหาหลายอย่างกับการแก้ปัญหาส่วนใหญ่บนอินเทอร์เน็ต ดังนั้นฉันจึงตัดสินใจติดตามซึ่งรวมถึงสาเหตุที่คำตอบที่ยอมรับไม่ควรได้รับการยอมรับ

สถานการณ์เริ่มต้น

ฉันต้องการคัดลอกจาวาสคริปต์ที่Objectมีลูก ๆ ของมันและลูก ๆ ของมันเป็นต้น แต่เนื่องจากผมไม่ได้ชนิดของนักพัฒนาปกติของฉันObjectมีปกติ properties , และแม้กระทั่งcircular structuresnested objects

ดังนั้นเรามาสร้างcircular structureและ a nested objectก่อน

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Let 's นำทุกอย่างเข้าด้วยกันในชื่อObjecta

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และเพื่อcircObject

โครงสร้างของต้นไม้ที่แบ่งปันใบไม้จะไม่ถูกคัดลอกพวกเขาจะกลายเป็นสองใบอิสระ:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

ข้อสรุป

ทางออกสุดท้ายที่ใช้การเรียกซ้ำและแคชอาจไม่ใช่วิธีที่ดีที่สุด แต่เป็นสำเนาที่แท้จริงของวัตถุ จะจัดการง่ายproperties, circular structuresและnested objectแต่มันจะเลอะอินสแตนซ์ของพวกเขาในขณะที่โคลน

jsfiddle


12
ดังนั้น conlcusion คือการหลีกเลี่ยงปัญหาที่ :)
Mikus

@ Mikus จนกว่าจะมีข้อมูลจำเพาะจริงซึ่งครอบคลุมมากกว่าเพียงแค่กรณีการใช้งานพื้นฐานใช่
Fabio Poloni

2
การวิเคราะห์ที่ถูกต้องของวิธีแก้ปัญหาที่ให้ไว้ข้างต้น แต่ข้อสรุปที่ผู้เขียนระบุไว้นั้นบ่งชี้ว่าไม่มีคำตอบสำหรับคำถามนี้
Amir Mog

2
เป็นที่น่าเสียดายที่ JS ไม่ได้มีฟังก์ชั่นการลอกแบบดั้งเดิม
l00k

1
ในบรรดาคำตอบทั้งหมดฉันรู้สึกว่ามันใกล้เคียงกับคำตอบที่ถูกต้อง
KTU

77

หากคุณโอเคกับสำเนาตื้นห้องสมุด underscore.js มีวิธีการโคลน

y = _.clone(x);

หรือคุณสามารถขยายได้เช่น

copiedObject = _.extend({},originalObject);

2
ขอบคุณ การใช้เทคนิคนี้บนเซิร์ฟเวอร์ Meteor
เทอร์โบ

ในการเริ่มต้นอย่างรวดเร็วกับ lodash ฉันขอแนะนำให้เรียนรู้ npm, Browserify, and lodash ฉันได้โคลนเพื่อทำงานกับ 'npm i - บันทึก lodash.clone' แล้ว 'var clone = require (' lodash.clone ');' หากต้องการรับงานคุณต้องมีเบราว์เซอร์ เมื่อคุณติดตั้งและเรียนรู้วิธีการทำงานคุณจะใช้ 'browserify yourfile.js> bundle.js; เริ่ม chrome index.html' ทุกครั้งที่คุณเรียกใช้รหัสของคุณ (แทนที่จะเข้าไปที่ Chrome โดยตรง) สิ่งนี้จะรวบรวมไฟล์ของคุณและไฟล์ทั้งหมดที่คุณต้องการจากโมดูล npm ไปยัง bundle.js คุณสามารถประหยัดเวลาและทำให้ขั้นตอนนี้เป็นอัตโนมัติด้วย Gulp ได้
Aaron Bell

65

ตกลงคิดว่าคุณมีวัตถุนี้ด้านล่างและคุณต้องการโคลน:

let obj = {a:1, b:2, c:3}; //ES6

หรือ

var obj = {a:1, b:2, c:3}; //ES5

คำตอบนั้นขึ้นอยู่กับECMA ที่คุณใช้เป็นหลักซึ่งES6+คุณสามารถใช้Object.assignในการโคลน:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

หรือใช้ตัวดำเนินการแบบแพร่กระจายเช่นนี้:

let cloned = {...obj}; //new {a:1, b:2, c:3};

แต่ถ้าคุณใช้ES5คุณสามารถใช้วิธีการต่าง ๆ แต่JSON.stringifyให้แน่ใจว่าคุณไม่ได้ใช้สำหรับการคัดลอกข้อมูลขนาดใหญ่ แต่มันอาจเป็นวิธีที่สะดวกในบรรทัดเดียวในหลาย ๆ กรณีดังนี้:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

คุณช่วยยกตัวอย่างสิ่งที่big chunk of dataจะเทียบเคียงได้ไหม 100kb? 100MB? ขอบคุณ!
user1063287

ใช่ @ user1063287 ซึ่งโดยทั่วไปแล้วเป็นข้อมูลที่ใหญ่กว่าประสิทธิภาพที่แย่ลง ... ดังนั้นมันขึ้นอยู่กับว่าไม่ใช่ kb, mb หรือ gb จริง ๆ แล้วมันเกี่ยวกับจำนวนครั้งที่คุณต้องการทำเช่นนั้น ... มันจะไม่ทำงานอีกด้วย สำหรับฟังก์ชั่นและของอื่น ๆ ...
Alireza

3
Object.assignทำสำเนาตื้น (เช่นเดียวกับการแพร่กระจาย @Alizera)
Bogdan D

คุณไม่สามารถใช้ let ใน es5: ^) @Alireza
SensationSama

40

โซลูชันที่ไม่เหมาะสมอย่างหนึ่งคือการใช้การเข้ารหัส JSON เพื่อทำสำเนาวัตถุที่ไม่มีวิธีการของสมาชิก วิธีการคือการ JSON เข้ารหัสวัตถุเป้าหมายของคุณแล้วถอดรหัสคุณได้รับสำเนาที่คุณกำลังมองหา คุณสามารถถอดรหัสได้หลายครั้งตามที่คุณต้องการทำสำเนาได้มากเท่าที่คุณต้องการ

แน่นอนว่าฟังก์ชั่นไม่ได้อยู่ใน JSON ดังนั้นจึงใช้ได้เฉพาะกับวัตถุที่ไม่มีวิธีการของสมาชิก

วิธีการนี้เหมาะสำหรับกรณีการใช้งานของฉันเนื่องจากฉันเก็บ JSON blobs ในที่เก็บคีย์ - ค่าและเมื่อพวกเขาถูกเปิดเผยเป็นวัตถุใน JavaScript API วัตถุแต่ละอันมีสำเนาสถานะดั้งเดิมของวัตถุจริง ๆ ดังนั้นเราจึง สามารถคำนวณเดลต้าหลังจากที่ผู้เรียกกลายพันธุ์วัตถุที่ถูกเปิดเผย

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

ทำไมฟังก์ชั่นไม่เป็นของ JSON? ฉันเห็นว่ามีการถ่ายโอนในฐานะ JSON มากกว่าหนึ่งครั้ง ...
the_drow

5
ฟังก์ชั่นไม่ได้เป็นส่วนหนึ่งของข้อมูลจำเพาะ JSON เนื่องจากไม่ใช่วิธีที่ปลอดภัย (หรือสมาร์ท) ในการถ่ายโอนข้อมูลซึ่งเป็นสิ่งที่ JSON สร้างขึ้น ฉันรู้ว่าตัวเข้ารหัส JSON ดั้งเดิมใน Firefox ไม่สนใจฟังก์ชั่นที่ส่งผ่านไป แต่ฉันไม่แน่ใจเกี่ยวกับพฤติกรรมของผู้อื่น
Kris Walker

1
@mark: { 'foo': function() { return 1; } }เป็นวัตถุที่สร้างขึ้นตามตัวอักษร
abarnert

ฟังก์ชัน @abarnert ไม่ใช่ข้อมูล "ฟังก์ชั่นตัวอักษร" เป็นชื่อเรียกที่ผิด - เนื่องจากฟังก์ชั่นสามารถมีรหัสโดยพลการรวมถึงการกำหนดและสิ่งต่างๆ
vemv

35

คุณสามารถใช้คุณสมบัติการแพร่กระจายเพื่อคัดลอกวัตถุโดยไม่มีการอ้างอิง แต่ระวัง (ดูความคิดเห็น) 'การคัดลอก' นั้นอยู่ที่ระดับออบเจ็กต์ / อาร์เรย์ที่ต่ำที่สุด คุณสมบัติที่ซ้อนอยู่ยังคงมีการอ้างอิง!


โคลนสมบูรณ์:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

โคลนที่มีการอ้างอิงในระดับที่สอง:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript ไม่รองรับโคลนนิ่งเชิงลึก ใช้ฟังก์ชั่นยูทิลิตี้ ตัวอย่างเช่น Ramda:

http://ramdajs.com/docs/#clone


1
สิ่งนี้ไม่ทำงาน ... มันอาจจะทำงานได้เมื่อ x จะเป็นอาร์เรย์เช่น x = ['ab', 'cd', ... ]
Kamil Kiełczewski

3
ใช้งานได้ แต่จำไว้ว่านี่เป็นสำเนาของตื้นดังนั้นการอ้างอิงเชิงลึกใด ๆ กับวัตถุอื่น ๆ
Bugs Bunny

โคลนบางส่วนสามารถเกิดขึ้นได้ด้วยวิธีนี้:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Cristian Traìna

25

สำหรับผู้ที่ใช้ AngularJS ยังมีวิธีการโดยตรงสำหรับการโคลนหรือการขยายวัตถุในห้องสมุดนี้

var destination = angular.copy(source);

หรือ

angular.copy(source, destination);

เพิ่มเติมในเอกสารประกอบเชิงมุม...


2
นี่คือสำเนา FYI ​​ที่ลึกซึ้ง
zamnuts

22

นี่คือฟังก์ชั่นที่คุณสามารถใช้ได้

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

10
คำตอบนี้ค่อนข้างใกล้ แต่ไม่ถูกต้องนัก ถ้าคุณลองโคลนวัตถุ Date คุณจะไม่ได้รับวันที่เหมือนกันเนื่องจากการเรียกฟังก์ชัน Date Constructor เริ่มต้น Date ใหม่ด้วย Date / Time ปัจจุบัน ค่านั้นไม่สามารถนับได้และจะไม่คัดลอกโดย for / in loop
A. Levy

ไม่สมบูรณ์แบบ แต่ดีสำหรับกรณีพื้นฐานเหล่านั้น เช่นอนุญาตให้มีการโคลนอาร์กิวเมนต์อย่างง่ายซึ่งอาจเป็นวัตถุพื้นฐานอาร์เรย์หรือสตริง
james_womack

upvoted newสำหรับอย่างถูกต้องโทรคอนสตรัคโดยใช้ คำตอบที่ยอมรับไม่ได้
GetFree

ทำงานบนโหนดทุกอย่างอื่น! ยังคงลิงก์ลิงก์อ้างอิงไว้
user956584

ความคิดซ้ำซากนั้นยอดเยี่ยม แต่ถ้าค่าคืออาร์เรย์มันจะใช้ได้ไหม
Q10Viking

22

คำตอบของ A.Levy เกือบจะเสร็จแล้วนี่คือผลงานเล็ก ๆ ของฉัน: มีวิธีจัดการกับการอ้างอิงแบบเรียกซ้ำดูบรรทัดนี้

if(this[attr]==this) copy[attr] = copy;

หากวัตถุนั้นเป็นองค์ประกอบของ XML DOM เราจะต้องใช้cloneNodeแทน

if(this.cloneNode) return this.cloneNode(true);

แรงบันดาลใจจากการศึกษาอย่างละเอียดถี่ถ้วนของ A.Levy และแนวทางการสร้างต้นแบบของ Calvin ฉันเสนอวิธีแก้ปัญหานี้:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

ดูบันทึกย่อของ Andy Burke ในคำตอบด้วย


3
Date.prototype.clone = function() {return new Date(+this)};
RobG

22

จากบทความนี้: วิธีคัดลอกอาร์เรย์และวัตถุใน Javascriptโดย Brian Huisman:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

4
สิ่งนี้อยู่ใกล้ แต่ไม่สามารถใช้ได้กับวัตถุใด ๆ ลองโคลนวัตถุ Date ด้วยสิ่งนี้ คุณสมบัติบางอย่างนั้นไม่สามารถนับได้ดังนั้นคุณสมบัติเหล่านี้จะไม่ปรากฏในลูป for / in ทั้งหมด
A. Levy

การเพิ่มต้นแบบวัตถุเช่นนี้ทำให้ jQuery แตกสำหรับฉัน แม้เมื่อฉันเปลี่ยนชื่อเป็น clone2
iPadDeveloper2011

3
@ iPadDeveloper2011 รหัสด้านบนมีจุดบกพร่องที่มันสร้างตัวแปรทั่วโลกที่เรียกว่า 'i' '(สำหรับฉันในสิ่งนี้)' แทนที่จะเป็น '(สำหรับ var i ในสิ่งนี้)' ฉันมีกรรมมากพอที่จะแก้ไขและแก้ไขและแก้ไขได้
mikemaccana

1
@Calvin: สิ่งนี้ควรสร้างเป็นคุณสมบัติที่ไม่นับได้มิฉะนั้น 'clone' จะปรากฏใน 'for' loops
mikemaccana

2
ทำไมไม่var copiedObj = Object.create(obj);เป็นวิธีที่ดีเช่นกัน?
Dan P.

19

ใน ES-6 คุณสามารถใช้ Object.assign (... ) Ex:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

การอ้างอิงที่ดีอยู่ที่นี่: https://googlechrome.github.io/samples/object-assign-es6/


12
มันไม่ลอกแบบวัตถุที่ลึก
สิงหาคม

นั่นคืองานที่มอบหมายไม่ใช่สำเนา clone.Title = "just a clone" หมายถึง obj.Title = "just a clone"
HoldOffHunger

@HoldOffHunger คุณเข้าใจผิด ตรวจสอบในคอนโซล JS ของเบราว์เซอร์ ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
collapsar

@collapsar: นั่นคือสิ่งที่ฉันตรวจสอบแล้ว console.log (คน) จะเป็น "Whazzup" ไม่ใช่ "Thor Odinson" ดูความคิดเห็นของสิงหาคม
HoldOffHunger

1
@HoldOffHunger ไม่เกิดขึ้นใน Chrome 60.0.3112.113 หรือใน Edge 14.14393 ความคิดเห็นของเดือนสิงหาคมไม่ได้นำมาใช้เนื่องจากค่าของคุณสมบัติดั้งเดิมของobjโคลนถูกโคลนอย่างแท้จริง ค่าคุณสมบัติที่เป็นวัตถุจะไม่ถูกโคลน
collapsar

18

ใน ECMAScript 2018

let objClone = { ...obj };

โปรดระวังว่าวัตถุที่ซ้อนกันยังคงถูกคัดลอกเป็นการอ้างอิง


1
ขอบคุณสำหรับคำใบ้ว่าวัตถุที่ซ้อนกันยังคงถูกคัดลอกเป็นข้อมูลอ้างอิง! ฉันเกือบจะบ้าเมื่อทำการดีบั๊กโค้ดของฉันเพราะฉันแก้ไขคุณสมบัติที่ซ้อนกันใน "โคลน" แต่ต้นฉบับได้รับการแก้ไข
Benny Neugebauer

นี่คือ ES2016 ไม่ 2018 และคำตอบนี้ได้รับเมื่อสองปีก่อน
Dan Dascalescu

ดังนั้นถ้าฉันต้องการสำเนาทรัพย์สินที่ซ้อนกันเช่นกัน
Sunil Garg

1
@SunilGarg เพื่อคัดลอกคุณสมบัติที่ซ้อนกันเช่นกันคุณสามารถใช้ const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

16

ใช้ Lodash:

var y = _.clone(x, true);

5
OMG มันจะเสียสติในการสร้างการโคลนซ้ำ นี่เป็นคำตอบเดียวที่มีสติ
Dan Ross

5
ฉันชอบ_.cloneDeep(x)ที่มันเป็นสิ่งเดียวกับด้านบน แต่อ่านได้ดีกว่า
garbanzio

14

สนใจในการโคลนวัตถุอย่างง่าย:

JSON.parse(JSON.stringify(json_original));

แหล่งที่มา: วิธีการคัดลอกวัตถุ JavaScript ไปยังตัวแปรใหม่ไม่โดยอ้างอิง?


ดีมาก - เรียบง่าย
Matt H

@MattH: คำตอบนี้ได้รับแล้วในปี 2012 คุณเห็นมันไหม โมฮัมเหม็ดคุณตรวจสอบคำตอบที่มีอยู่ก่อนทำซ้ำคำใดคำหนึ่งหรือไม่
Dan Dascalescu

กันทางเดียว ty ไม่เคยคิดอย่างนั้น
1-14x0r

13

คุณสามารถโคลนวัตถุและลบการอ้างอิงใด ๆ จากวัตถุก่อนหน้าโดยใช้รหัสบรรทัดเดียว เพียงทำ:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

สำหรับเบราว์เซอร์ / เครื่องมือที่ไม่รองรับ Object.create คุณสามารถใช้ polyfill นี้:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

1
+1 Object.create(...)ดูเหมือนจะเป็นวิธีที่แน่นอน
René Nyffenegger

คำตอบที่สมบูรณ์แบบ บางทีคุณอาจจะเพิ่มคำอธิบายสำหรับObject.hasOwnProperty? วิธีนี้ทำให้ผู้คนรู้วิธีป้องกันการค้นหาลิงค์ต้นแบบ
froginvasion

ทำงานได้ดี แต่โพลีฟิลใช้งานได้กับเบราว์เซอร์ใดบ้าง
Ian Lunn

11
นี่เป็นการสร้าง obj2 ด้วย obj1 เนื่องจากเป็นเครื่องต้นแบบ ใช้งานได้เพราะคุณแชโดว์textสมาชิกใน obj2 เท่านั้น คุณไม่ได้ทำสำเนาเพียงแค่เลื่อนไปที่ลูกโซ่ต้นแบบเมื่อไม่พบสมาชิกใน obj2
Nick Desaulniers

2
สิ่งนี้ไม่ได้สร้าง "ไม่มีการอ้างอิง" มันแค่ย้ายการอ้างอิงไปยังต้นแบบ มันยังคงเป็นข้อมูลอ้างอิง หากสถานที่ให้บริการการเปลี่ยนแปลงในต้นฉบับดังนั้นคุณสมบัติต้นแบบใน "โคลน" จะ มันไม่ได้เป็นโคลนเลย
Jimbo Jonny

13

คำตอบใหม่สำหรับคำถามเก่า! ถ้าคุณมีความสุขที่ได้ใช้ ECMAScript 2016 (ES6) กับSpread Syntaxมันเป็นเรื่องง่าย

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

นี่เป็นวิธีการที่สะอาดสำหรับสำเนาวัตถุที่ตื้น การทำสำเนาลึกหมายถึงการทำสำเนาใหม่ของทุกค่าในวัตถุที่ซ้อนกันซ้ำทุกครั้งซึ่งต้องใช้กับโซลูชันที่หนักกว่าด้านบน

JavaScript พัฒนาอย่างต่อเนื่อง


2
มันไม่ทำงานเมื่อคุณมีฟังก์ชั่นที่กำหนดไว้ในวัตถุ
Petr Marek

เท่าที่ฉันเห็นผู้ประกอบการแพร่กระจายทำงานได้เฉพาะกับ iterables - developer.mozilla.orgพูดว่า: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Oleh

@Oleh ดังนั้นใช้ `{... obj} แทน [... obj];`
manikant gautam

@manikantgautam ฉันใช้ Object.assign () มาก่อน แต่ตอนนี้แท้จริงแล้วไวยากรณ์การกระจายวัตถุได้รับการสนับสนุนใน Chrome ล่าสุด, Firefox (ยังไม่ได้อยู่ใน Edge และ Safari) ข้อเสนอ ECMAScript ของมัน ... แต่บาเบลให้การสนับสนุนเท่าที่ฉันเห็นดังนั้นมันจึงปลอดภัยที่จะใช้
โอ

12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

โซลูชัน ES6 ถ้าคุณต้องการโคลน (ตื้น) คลาสอินสแตนซ์และไม่ใช่แค่วัตถุคุณสมบัติ


สิ่งนี้แตกต่างจากlet cloned = Object.assign({}, obj)อย่างไร
ceztko

10

ฉันคิดว่ามีคำตอบที่ง่ายและทำงานได้ ในการทำสำเนาลึกมีข้อกังวลสองประการ:

  1. รักษาคุณสมบัติให้เป็นอิสระต่อกัน
  2. และทำให้วิธีการมีชีวิตอยู่บนวัตถุที่โคลน

ดังนั้นฉันคิดว่าวิธีแก้ปัญหาอย่างง่ายจะทำให้เป็นอันดับแรกและ deserialize จากนั้นทำการมอบหมายให้มันเพื่อคัดลอกฟังก์ชั่นด้วย

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

แม้ว่าคำถามนี้มีคำตอบมากมาย แต่ฉันหวังว่าคำถามนี้จะช่วยได้เช่นกัน


แต่ถ้าฉันกำลังได้รับอนุญาตให้นำเข้า lodash ผมชอบใช้ cloneDeeplodash
ConductedClever

2
ฉันใช้ JSON.parse (JSON.stringify (แหล่งที่มา)) ทำงานอยู่เสมอ
Misha

2
@Misha ด้วยวิธีนี้คุณจะพลาดฟังก์ชั่น คำว่า 'ทำงาน' มีความหมายมากมาย
ConductedClever

และโปรดจำไว้ว่าวิธีที่ฉันพูดถึงจะมีการคัดลอกเฉพาะหน้าที่ของเลเยอร์แรกเท่านั้น ดังนั้นถ้าเรามีวัตถุบางอย่างอยู่ภายในซึ่งกันและกันวิธีเดียวคือคัดลอกฟิลด์ตามฟิลด์ซ้ำ
ConductedClever

9

สำหรับการคัดลอกและโคลนแบบลึก JSON.stringify จากนั้น JSON.parse วัตถุ:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

ค่อนข้างฉลาด ... ข้อเสียของวิธีนี้คืออะไร?
Aleks

8

นี่คือการปรับรหัสของ A. Levy เพื่อจัดการการโคลนนิ่งของฟังก์ชันและการอ้างอิงแบบวนซ้ำ / หลายรอบ - นี่หมายความว่าถ้าคุณสมบัติสองอย่างในต้นไม้ที่ถูกโคลนเป็นการอ้างอิงวัตถุเดียวกันต้นไม้วัตถุที่ถูกโคลนจะมีสิ่งเหล่านี้ คุณสมบัติชี้ไปที่หนึ่งและโคลนเดียวกันของวัตถุที่อ้างอิง นอกจากนี้ยังแก้กรณีของการพึ่งพาแบบวนรอบซึ่งหากปล่อยทิ้งไว้ไม่ถูกจัดการจะนำไปสู่การวนซ้ำไม่สิ้นสุด ความซับซ้อนของอัลกอริทึมคือ O (n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") 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; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


        throw new Error("Unable to copy obj! Its type isn't supported.");
    }
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

ทดสอบอย่างรวดเร็ว

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

1
ณ สิ้นเดือนกันยายน 2016 นี้เป็นเพียงการแก้ปัญหาที่ถูกต้องกับคำถาม
DomQ

6

ฉันแค่อยากจะเพิ่มทุกอย่าง Object.createโซลูชั่นในโพสต์นี้ว่ามันไม่ทำงานในแบบที่ต้องการด้วย nodejs

ใน Firefox ผลลัพธ์ของ

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

คือ

{test:"test"}.

ใน nodejs มันคือ

{}

นี่คือการสืบทอดต้นแบบไม่ใช่การโคลน
d13

1
@ d13 ในขณะที่การโต้แย้งของคุณถูกต้องโปรดทราบว่าไม่มีวิธีที่ได้มาตรฐานใน JavaScript เพื่อโคลนวัตถุ นี่คือการถ่ายทอดทางพันธุกรรมต้นแบบ แต่มันสามารถใช้เป็นโคลนอย่างไรก็ตามถ้าคุณเข้าใจแนวคิด
froginvasion

@froginvasion ปัญหาเดียวของการใช้ Object.create คือวัตถุและอาร์เรย์ที่ซ้อนกันเป็นเพียงตัวชี้อ้างอิงถึงวัตถุและอาร์เรย์ที่ซ้อนกันของต้นแบบ jsbin.com/EKivInO/2/edit?js,console ในทางเทคนิควัตถุ "ที่ลอกแบบแล้ว" ควรมีคุณสมบัติที่เป็นเอกลักษณ์ของตัวเองที่ไม่ได้ใช้ร่วมกันอ้างอิงถึงคุณสมบัติของวัตถุอื่น
d13

@ d13 โอเคฉันเห็นประเด็นของคุณแล้ว แต่สิ่งที่ฉันหมายถึงคือคนจำนวนมากเกินไปแปลกกับแนวคิดของการถ่ายทอดทางพันธุกรรมต้นแบบและสำหรับฉันล้มเหลวที่จะเรียนรู้วิธีการทำงาน หากฉันไม่เข้าใจผิดตัวอย่างของคุณสามารถแก้ไขได้โดยเพียงแค่โทรObject.hasOwnPropertyเพื่อตรวจสอบว่าคุณเป็นเจ้าของอาร์เรย์หรือไม่ ใช่สิ่งนี้จะเพิ่มความซับซ้อนเพิ่มเติมเพื่อจัดการกับการสืบทอดต้นแบบ
froginvasion

6
function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};

2
if(!src && typeof src != "object"){. ผมคิดว่าควรจะเป็นไม่ได้|| &&
MikeM

6

เนื่องจากความคิดดังกล่าวระบุว่าวัตถุที่จะทำการโคลนนั้นเป็นวัตถุ 'ที่สร้างขึ้นตามตัวอักษร' การแก้ปัญหาอาจเป็นการสร้างวัตถุเพียงหลาย ๆ ครั้งแทนที่จะทำการโคลนนิ่งตัวอย่างของวัตถุ:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();

6

ฉันเขียนการใช้งานของฉันเอง ไม่แน่ใจว่ามันจะเป็นทางออกที่ดีกว่าหรือไม่:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

ต่อไปนี้คือการใช้งาน:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}

ไม่ทำงานกับวัตถุของฉันแม้ว่ากรณีของฉันจะซับซ้อนเล็กน้อย
Sajuuk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.