วิธีที่มีประสิทธิภาพมากที่สุดในการโคลนวัตถุใน JavaScript คืออะไร


5180

วิธีที่มีประสิทธิภาพมากที่สุดในการโคลนวัตถุ JavaScript คืออะไร? ฉันเห็นว่าobj = eval(uneval(o));มีการใช้งาน แต่ไม่ได้มาตรฐานและรองรับโดย Firefoxเท่านั้น

ฉันทำสิ่งต่าง ๆ เช่นobj = JSON.parse(JSON.stringify(o));แต่ถามถึงประสิทธิภาพ

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


566
Eval ไม่ใช่ความชั่วร้าย การใช้ eval ไม่ดีคือ หากคุณกลัวผลข้างเคียงคุณจะใช้ผิด ผลข้างเคียงที่คุณกลัวคือเหตุผลที่ใช้ มีใครตอบคำถามของคุณตามจริงไหม?
James

15
การโคลนวัตถุเป็นธุรกิจที่ยุ่งยากโดยเฉพาะอย่างยิ่งกับวัตถุที่กำหนดเองของคอลเลกชันเอง ซึ่งอาจเป็นสาเหตุที่ไม่มีวิธีออกนอกกรอบที่จะทำ
b01

12
eval()เป็นความคิดที่ไม่ดีเพราะoptimisers เครื่องยนต์ Javascript evalจำนวนมากต้องปิดเมื่อจัดการกับตัวแปรที่มีการตั้งค่าผ่านทาง เพียงแค่มีeval()ในรหัสของคุณสามารถนำไปสู่ประสิทธิภาพที่แย่ลง
user56reinstatemonica8

2
อาจเป็นวิธีที่ซ้ำซ้อนที่สุดในการเลียนแบบวัตถุ JavaScript
John Slegers

12
โปรดทราบว่าJSONวิธีการนี้จะสูญเสียประเภท Javascript ใด ๆ ที่ไม่เทียบเท่าใน JSON ตัวอย่างเช่น: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))จะสร้าง{a: null, b: null, c: null, g: false}
oriadam

คำตอบ:


4732

การโคลนนิ่งลึกแบบเนทีฟ

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

การโคลนอย่างรวดเร็วด้วยการสูญเสียข้อมูล - JSON.parse / stringify

หากคุณไม่ได้ใช้Dateของฟังก์ชั่นundefined, Infinity, regexps, Maps, ชุด Blobs, filelists, ImageDatas อาร์เรย์เบาบางประเภทอาร์เรย์หรือชนิดอื่น ๆ ที่ซับซ้อนภายในวัตถุของคุณเป็นหนึ่งซับง่ายมากที่จะโคลนลึกวัตถุคือ:

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'
  re: /.*/,  // lost
}
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()

ดูคำตอบของ Corbanสำหรับการวัดประสิทธิภาพ

การโคลนที่เชื่อถือได้โดยใช้ห้องสมุด

เนื่องจากการโคลนวัตถุนั้นไม่สำคัญ (ประเภทที่ซับซ้อน, การอ้างอิงแบบวงกลม, ฟังก์ชั่นอื่น ๆ ), ไลบรารีที่สำคัญส่วนใหญ่จึงให้ฟังก์ชันการโคลนวัตถุ อย่าบูรณาการล้อ - หากคุณใช้ห้องสมุดอยู่แล้วให้ตรวจสอบว่ามีฟังก์ชันการโคลนวัตถุหรือไม่ ตัวอย่างเช่น,

  • Lodash - cloneDeep; สามารถนำเข้าแยกต่างหากผ่านโมดูลlodash.clonedeepและอาจเป็นทางเลือกที่ดีที่สุดของคุณหากคุณยังไม่ได้ใช้ห้องสมุดที่มีฟังก์ชั่นการโคลนนิ่งลึก
  • AngularJS - angular.copy
  • jQuery - jQuery.extend(true, { }, oldObject); .clone()โคลนองค์ประกอบ DOM เท่านั้น

ES6

เพื่อความสมบูรณ์ทราบว่า ES6 มีสองกลไกสำเนาตื้นObject.assign()และไวยากรณ์การแพร่กระจาย ซึ่งคัดลอกค่าของคุณสมบัติของตัวเองที่นับได้ทั้งหมดจากวัตถุหนึ่งไปยังอีก ตัวอย่างเช่น:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.jsที่บรรทัด 276 (มีรหัสเล็กน้อยที่ทำอย่างอื่นนอกจากรหัสสำหรับ "วิธีการทำสิ่งนี้ใน JS" มีอยู่ :)
Rune FS

7
นี่คือรหัส JS ที่อยู่เบื้องหลังสำเนาลึกของ jQuery สำหรับผู้ที่สนใจ: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
ว้าว! ชัดเจนมาก: ไม่รู้เลยว่าทำไมคำตอบนี้จึงถูกเลือกเป็นคำตอบที่ถูกต้องนี่เป็นคำตอบสำหรับคำตอบที่ให้ไว้ด้านล่าง: stackoverflow.com/a/122190/6524 (ซึ่งเป็นการแนะนำ.clone()ซึ่งไม่ใช่รหัสที่ถูกต้องที่จะเป็น ใช้ในบริบทนี้) น่าเสียดายที่คำถามนี้ผ่านการแก้ไขมากมายการสนทนาเริ่มแรกไม่ชัดเจนอีกต่อไป โปรดทำตามคำแนะนำของ Corban และเขียนลูปหรือคัดลอกคุณสมบัติโดยตรงไปยังวัตถุใหม่หากคุณสนใจความเร็ว หรือทดสอบด้วยตัวคุณเอง!
John Resig

9
นี่คือคำถาม JavaScript (ไม่พูดถึง jQuery)
gphilip

60
เราจะทำสิ่งนี้โดยไม่ใช้ jQuery ได้อย่างไร
Awesomeness01

2264

ชำระเงินมาตรฐานนี้: http://jsben.ch/#/bWfk9

ในการทดสอบก่อนหน้าของฉันที่ฉันพบว่าความเร็วเป็นปัญหาหลัก

JSON.parse(JSON.stringify(obj))

เพื่อเป็นวิธีที่ช้าที่สุดในการโคลนวัตถุ (ช้ากว่าjQuery.extendด้วยการdeepตั้งค่าสถานะจริง 10-20%)

jQuery.extend ค่อนข้างเร็วเมื่อdeepตั้งค่าสถานะเป็นfalse(โคลนตื้น) มันเป็นตัวเลือกที่ดีเพราะมันมีตรรกะพิเศษบางอย่างสำหรับการตรวจสอบประเภทและไม่คัดลอกคุณสมบัติที่ไม่ได้กำหนด ฯลฯ แต่สิ่งนี้จะทำให้คุณช้าลงเล็กน้อย

ถ้าคุณรู้โครงสร้างของวัตถุที่คุณพยายามโคลนหรือหลีกเลี่ยงอาร์เรย์ที่ซ้อนกันลึกคุณสามารถเขียนfor (var i in obj)วนรอบอย่างง่าย ๆเพื่อโคลนวัตถุของคุณขณะตรวจสอบ hasOwnProperty และมันจะเร็วกว่า jQuery มาก

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

เอ็นจินการติดตาม JavaScript จะดูดการปรับfor..inลูปให้เหมาะสมและการตรวจสอบ hasOwnProperty จะทำให้คุณช้าลงเช่นกัน โคลนด้วยตนเองเมื่อต้องมีความเร็วแน่นอน

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

ระวังการใช้JSON.parse(JSON.stringify(obj))วิธีการกับDateวัตถุ - JSON.stringify(new Date())คืนค่าการแสดงสตริงของวันที่ในรูปแบบ ISO ซึ่งJSON.parse() จะไม่แปลงกลับเป็นDateวัตถุ ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติม

นอกจากนี้โปรดทราบว่าอย่างน้อยใน Chrome 65 การโคลนนิ่งดั้งเดิมไม่ใช่วิธีที่จะไป ตาม JSPerf ละครโคลนพื้นเมืองโดยการสร้างฟังก์ชั่นใหม่เกือบ800xช้ากว่าการใช้ JSON.stringify ซึ่งเป็นไปอย่างรวดเร็วอย่างไม่น่าเชื่อทุกทางทั่วกระดาน

อัพเดทสำหรับ ES6

หากคุณกำลังใช้ Javascript ES6 ลองใช้วิธีเนทีฟนี้เพื่อคัดลอกหรือคัดลอกตื้น

Object.assign({}, obj);

4
@trysis Object.create ไม่ได้คัดลอกวัตถุกำลังใช้วัตถุต้นแบบ ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser

105
วิธีการนี้จะลบkeysข้อมูลของคุณobjectซึ่งมีfunctionsค่าเหมือนกันเพราะJSONฟังก์ชั่นไม่รองรับ
Karlen Kishmiryan

39
โปรดทราบว่าการใช้JSON.parse(JSON.stringify(obj))กับวัตถุวันที่จะแปลงวันที่กลับเป็นUTCในรูปแบบสตริงในรูปแบบISO8601
dnlgmzddr

31
วิธีการของ JSON ยังทำให้เกิดการอ้างอิงแบบวนรอบ
รวย remer

28
@velop, Object.assign ({}, objToClone) ดูเหมือนว่ามันจะทำโคลนแบบตื้น - แม้ว่าจะใช้มันในขณะที่เล่นอยู่ในคอนโซลเครื่องมือ dev แล้วก็ตามการโคลนวัตถุยังชี้ไปที่การอ้างอิงของวัตถุที่ถูกโคลน ดังนั้นฉันจึงไม่คิดว่ามันใช้ได้จริงที่นี่
Garrett Simpson

473

สมมติว่าคุณมีตัวแปรเท่านั้นและไม่มีฟังก์ชั่นใด ๆ ในวัตถุของคุณคุณสามารถใช้:

var newObject = JSON.parse(JSON.stringify(oldObject));

86
ข้อเสียของวิธีการนี้ในขณะที่ฉันเพิ่งพบว่าถ้าวัตถุของคุณมีฟังก์ชั่นใด ๆ (ฉันมี getters & setters ภายใน) แล้วสิ่งเหล่านี้จะหายไปเมื่อถูกทำให้เป็นเส้น .. หากนั่นคือทั้งหมดที่คุณต้องการวิธีนี้เป็นเรื่องปกติ ..
Markive

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

35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer

32
เทคนิคนี้จะทำลายDateวัตถุทั้งหมดที่เก็บอยู่ภายในวัตถุด้วยการแปลงให้เป็นรูปแบบสตริง
fstab

13
มันจะล้มเหลวในการคัดลอกสิ่งที่ไม่ได้เป็นส่วนหนึ่งของข้อมูลจำเพาะ JSON ( json.org )
cdmckay

397

การโคลนโครงสร้าง

มาตรฐาน HTML รวมถึงอัลกอริทึมการโคลนนิ่ง / การทำให้เป็นอนุกรมที่สามารถสร้างโคลนของวัตถุได้ มันยัง จำกัด เฉพาะบางประเภทในตัว แต่นอกเหนือจากบางประเภทที่รองรับโดย JSON มันยังรองรับวันที่ RegExps แผนที่ชุด Blobs, FileLists, ImageDatas, เบาบางอาร์เรย์อาร์เรย์ที่พิมพ์และอาจเพิ่มเติมในอนาคต . นอกจากนี้ยังเก็บรักษาการอ้างอิงภายในข้อมูลที่ถูกโคลนทำให้สามารถสนับสนุนโครงสร้างแบบวนซ้ำและแบบวนซ้ำซึ่งจะทำให้เกิดข้อผิดพลาดสำหรับ JSON

การสนับสนุนใน Node.js: ทดลอง🙂

v8โมดูลใน Node.js ปัจจุบัน ( ณ วันที่โหนด 11) exposes อนุกรม API โครงสร้างโดยตรงแต่การทำงานนี้จะยังคงระบุว่าเป็น "การทดลอง" และอาจมีการเปลี่ยนแปลงหรือการกำจัดในรุ่นอนาคต หากคุณกำลังใช้รุ่นที่เข้ากันได้การโคลนวัตถุนั้นง่ายพอ ๆ กับ:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

การสนับสนุนโดยตรงในเบราว์เซอร์: บางทีในที่สุด? 😐

เบราว์เซอร์ยังไม่ให้ติดต่อโดยตรงสำหรับขั้นตอนวิธีการโคลนโครงสร้าง แต่ทั่วโลกstructuredClone()ฟังก์ชั่นที่ได้รับการกล่าวถึงในWHATWG / html # 793 บน GitHub ตามที่เสนอในปัจจุบันการใช้เพื่อวัตถุประสงค์ส่วนใหญ่จะง่ายเหมือน:

const clone = structuredClone(original);

การใช้งานโคลนโครงสร้างของเบราว์เซอร์จะถูกเปิดเผยทางอ้อมเว้นแต่จะมีการส่ง

วิธีแก้ปัญหาแบบอะซิงโครนัส: ใช้งานได้ 😕

วิธีลดค่าใช้จ่ายในการสร้างโครงสร้างโคลนด้วย API ที่มีอยู่คือการโพสต์ข้อมูลผ่านพอร์ตหนึ่งในMessageChannels พอร์ตอื่น ๆ จะออกมาเป็นเหตุการณ์ที่มีโคลนโครงสร้างของที่แนบมาmessage .dataน่าเสียดายที่การฟังเหตุการณ์เหล่านี้ไม่จำเป็นต้องเป็นแบบอะซิงโครนัสและทางเลือกแบบซิงโครนัสนั้นใช้งานได้น้อยกว่า

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

ตัวอย่างการใช้:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

วิธีแก้ปัญหาแบบซิงโครนัส: น่ากลัว! 🤢

ไม่มีตัวเลือกที่ดีสำหรับการสร้างโคลนที่มีโครงสร้างพร้อมกัน นี่คือแฮ็กทำไม่ได้สองสามอันแทน

history.pushState()และทั้งสร้างโคลนโครงสร้างของอาร์กิวเมนต์แรกของพวกเขาและกำหนดค่าที่จะhistory.replaceState() history.stateคุณสามารถใช้สิ่งนี้เพื่อสร้างโคลนโครงสร้างของวัตถุใด ๆ เช่นนี้

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

ตัวอย่างการใช้:

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

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

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

ตัวอย่างการใช้:


3
@rynah ฉันเพิ่งดูข้อมูลจำเพาะอีกครั้งและคุณถูก: history.pushState()และhistory.replaceState()วิธีการทั้งสองตั้งค่าhistory.stateการโคลนแบบโครงสร้างของอาร์กิวเมนต์แรกของพวกเขา แปลกเล็กน้อย แต่ก็ใช้งานได้ ฉันกำลังปรับปรุงคำตอบของฉันตอนนี้
Jeremy Banks

40
นี่มันผิดมาก! API นั้นไม่ได้มีไว้สำหรับใช้ในวิธีนี้
Fardin K.

209
ในฐานะที่เป็นคนที่ใช้ pushState ใน Firefox ฉันรู้สึกได้ถึงความภาคภูมิใจและความรังเกียจที่แฮ็คนี้ ทำได้ดีมาก
Justin L.

pushState หรือการแฮ็คการแจ้งเตือนไม่ทำงานสำหรับวัตถุบางประเภทเช่น Function
Shishir Arora

323

หากไม่มีสิ่งก่อสร้างใด ๆ คุณสามารถลอง:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

20
โซลูชัน JQuery จะทำงานกับองค์ประกอบ DOM แต่ไม่ใช่แค่วัตถุใด ๆ Mootools มีขีด จำกัด เดียวกัน หวังว่าพวกเขาจะมี "โคลน" ทั่วไปสำหรับวัตถุใด ๆ ... โซลูชันแบบเรียกซ้ำควรทำงานได้ทุกอย่าง มันอาจเป็นวิธีที่จะไป
jschrab

5
ฟังก์ชั่นนี้แบ่งถ้าวัตถุที่ถูกโคลนมีคอนสตรัคที่ต้องใช้พารามิเตอร์ ดูเหมือนว่าเราสามารถเปลี่ยนเป็น "var temp = new Object ()" และใช้งานได้ในทุกกรณีใช่หรือไม่
Andrew Arnott

3
แอนดรูถ้าคุณเปลี่ยนเป็น var temp = new Object () จากนั้นโคลนของคุณจะไม่มีต้นแบบเดียวกันกับวัตถุดั้งเดิม ลองใช้: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = newProto ใหม่ (); '
limscoder

1
คล้ายกับคำตอบของ limscoder โปรดดูคำตอบของฉันด้านล่างเกี่ยวกับวิธีการทำเช่นนี้โดยไม่ต้องเรียก constructor: stackoverflow.com/a/13333781/560114
Matt Browne

3
สำหรับวัตถุที่มีการอ้างอิงถึงส่วนย่อย (เช่นเครือข่ายของวัตถุ) สิ่งนี้ไม่ทำงาน: หากการอ้างอิงสองรายการชี้ไปที่วัตถุย่อยเดียวกันสำเนาจะมีสำเนาที่แตกต่างกันสองชุด และถ้ามีการอ้างอิงแบบเรียกซ้ำฟังก์ชันจะไม่ยุติ (อย่างน้อยก็ไม่ใช่ในแบบที่คุณต้องการ :-) สำหรับกรณีทั่วไปเหล่านี้คุณต้องเพิ่มพจนานุกรมของวัตถุที่คัดลอกแล้วและตรวจสอบว่าคุณคัดลอกมาแล้วหรือไม่ ... การเขียนโปรแกรมมีความซับซ้อนเมื่อคุณใช้ภาษาง่าย ๆ
virtualnobi

153

วิธีที่มีประสิทธิภาพในการโคลน (ไม่ใช่การโคลนลึก) วัตถุในโค้ดหนึ่งบรรทัด

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;
    }
  });
}

82
สิ่งนี้ไม่ได้คัดลอกซ้ำดังนั้นจึงไม่ได้เสนอวิธีแก้ไขปัญหาการโคลนวัตถุจริงๆ
mwhite

5
วิธีนี้ใช้ได้แม้ว่าฉันจะทดสอบไม่กี่และ _.extend ({}, (obj)) คือ BY FAR เร็วที่สุด: 20x เร็วกว่า JSON.parse และ 60% เร็วกว่า Object.assign มันคัดลอกวัตถุย่อยทั้งหมดได้ค่อนข้างดี
Nico

11
@mwhite มีความแตกต่างระหว่างโคลนและโคลนลึก คำตอบนี้จริงแล้วเป็นโคลน แต่มันไม่ได้โคลนลึก
Meirion Hughes

57
op ขอโคลนลึก นี่ไม่ได้ทำการโคลนแบบลึก
user566245

9
วิธีนี้ทำสำเนาตื้น ๆและไม่ใช่สำเนาลึก ! ด้วยเหตุนี้มันเป็นคำตอบที่ผิดอย่างสมบูรณ์ !
Bharata

97

รหัส:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

ทดสอบ:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

3
สิ่งที่เกี่ยวกับvar obj = {}และobj.a = obj
neaumusic

5
ฉันไม่เข้าใจฟังก์ชั่นนี้ สมมติว่าfrom.constructorเป็นDateตัวอย่าง การifทดสอบครั้งที่สามจะมาถึงอย่างไรเมื่อการifทดสอบครั้งที่ 2 ประสบความสำเร็จ & ทำให้ฟังก์ชันส่งคืน (ตั้งแต่Date != Object && Date != Array)
Adam McKee

1
@AdamMcKee เพราะผ่านอาร์กิวเมนต์จาวาสคริปต์และการกำหนดตัวแปรเป็นเรื่องยุ่งยาก วิธีการนี้ใช้งานได้ดีรวมถึงวันที่ (ซึ่งแน่นอนจะถูกจัดการโดยการทดสอบที่สอง) - ซอไปทดสอบที่นี่: jsfiddle.net/zqv9q9c6
brichins

1
@NickSweeting: ลอง - อาจใช้งานได้ ถ้าไม่ - แก้ไขและปรับปรุงคำตอบ นั่นคือวิธีการทำงานที่นี่ในชุมชน :)
Kamarey

1
ฟังก์ชั่นนี้ไม่ได้คัดลอก regex ในการทดสอบเงื่อนไข "from.constructor! = Object && from.constructor! = Array" จะส่งกลับค่าจริงสำหรับตัวสร้างอื่น ๆ เช่น Number, Date และอื่น ๆ
aMarCruz

95

นี่คือสิ่งที่ฉันกำลังใช้:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

8
ดูเหมือนจะไม่ถูกต้อง cloneObject({ name: null })=>{"name":{}}
Niyaz

13
นี่เป็นเพราะอีกสิ่งที่โง่ในจาวาสคริปต์typeof null > "object"แต่Object.keys(null) > TypeError: Requested keys of a value that is not an object.เปลี่ยนเงื่อนไขเป็นif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us

สิ่งนี้จะกำหนดคุณสมบัติที่นับได้ของobjให้กับโคลนโดยตรงและถือว่าobjเป็นวัตถุธรรมดา
RobG

นี่จะทำให้อาร์เรย์ยุ่งเหยิงซึ่งจะถูกแปลงเป็นวัตถุด้วยปุ่มตัวเลข
Blade

ไม่ใช่ปัญหาถ้าคุณไม่ใช้ null
Jorge Bucaran

78

คัดลอกอย่างลึกซึ้งตามประสิทธิภาพ: จัดอันดับจากดีที่สุดไปหาต่ำที่สุด

  • การกำหนดใหม่ "=" (อาร์เรย์สตริง, อาร์เรย์ตัวเลข - เท่านั้น)
  • Slice (อาร์เรย์สตริง, อาร์เรย์ตัวเลข - เท่านั้น)
  • การต่อข้อมูล (สตริงอาร์เรย์, อาร์เรย์ตัวเลข - เท่านั้น)
  • ฟังก์ชั่นที่กำหนดเอง: สำหรับวงวนหรือสำเนาซ้ำ
  • jQuery's $ .extend
  • JSON.parse (อาร์เรย์สตริง, อาร์เรย์ตัวเลข, อ็อบเจ็กต์อาร์เรย์ - เท่านั้น)
  • Underscore.js 's _.clone (อาร์เรย์สตริงอาร์เรย์จำนวน - เท่านั้น)
  • Lo-Dash's _.cloneDeep

คัดลอกอาร์เรย์ของสตริงหรือตัวเลขอย่างลึกซึ้ง (หนึ่งระดับ - ไม่มีตัวชี้อ้างอิง):

เมื่ออาร์เรย์มีตัวเลขและสตริง - ฟังก์ชันเช่น .slice (), .concat (), .splice (), ตัวดำเนินการกำหนดค่า "=", และฟังก์ชันโคลนของ Underscore.js จะทำการคัดลอกองค์ประกอบของอาร์เรย์อย่างละเอียด

ในกรณีที่การโอนสิทธิมีประสิทธิภาพที่เร็วที่สุด:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

และ .slice () มีประสิทธิภาพที่ดีกว่า. catcat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

คัดลอกอาเรย์ของวัตถุอย่างลึก (อย่างน้อยสองระดับ - พอยน์เตอร์อ้างอิง):

var arr1 = [{object:'a'}, {object:'b'}];

เขียนฟังก์ชั่นที่กำหนดเอง (มีประสิทธิภาพเร็วกว่า $ .extend () หรือ JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

ใช้ฟังก์ชั่นยูทิลิตี้ของบุคคลที่สาม:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

โดยที่ $ .extend ของ jQuery มีประสิทธิภาพที่ดีกว่า:


ฉันทดสอบน้อยและ _.extend ({}, (obj)) คือ BY FAR ที่เร็วที่สุด: เร็วกว่า JSON.parse 20x และเร็วกว่า Object.assign 60% มันคัดลอกวัตถุย่อยทั้งหมดได้ค่อนข้างดี
Nico

4
ตัวอย่างทั้งหมดของคุณตื้นเขินหนึ่งระดับ นี่ไม่ใช่คำตอบที่ดี คำถามเกี่ยวกับการโคลนนิ่งที่ลึกอย่างน้อยสองระดับ
Karl Morrison

1
สำเนาลึกคือเมื่อวัตถุถูกคัดลอกใน 'ครบถ้วนโดยไม่ใช้ตัวชี้การอ้างอิงไปยังวัตถุอื่น เทคนิคภายใต้ส่วน "คัดลอกอาร์เรย์ของวัตถุ" เช่น jQuery.extend () และฟังก์ชันที่กำหนดเอง (ซึ่งเรียกซ้ำ) คัดลอกวัตถุด้วย "อย่างน้อยสองระดับ" ดังนั้นไม่มีตัวอย่างทั้งหมดที่เป็นสำเนา "หนึ่งระดับ"
tfmontague

1
ฉันชอบฟังก์ชั่นการคัดลอกที่กำหนดเองของคุณ แต่คุณควรยกเว้นค่า Null มิฉะนั้นค่า Null ทั้งหมดจะถูกแปลงเป็นวัตถุเช่น:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi

2
@HossamMourad - ข้อผิดพลาดได้รับการแก้ไขโดย Josi เมื่อวันที่ 1 กุมภาพันธ์ (ในความคิดเห็นด้านบน) และฉันไม่สามารถอัปเดตคำตอบได้อย่างถูกต้อง ขออภัยที่ข้อผิดพลาดนี้ส่งผลให้ refactor ของฐานรหัสของคุณ
tfmontague

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

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});

คำตอบที่ดี แต่ล้มเหลวสำหรับการอ้างอิงแบบวงกลม
ลุค

59

คัดลอกวัตถุใน JavaScript อย่างลึกซึ้ง (ฉันคิดว่าดีที่สุดและง่ายที่สุด)

1. การใช้ JSON.parse (JSON.stringify (object));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2. ใช้วิธีการที่สร้างขึ้น

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. ใช้ลิงก์_.cloneDeepลิงก์ของ Lo-Dash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. การใช้วิธี Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

แต่ผิดเมื่อ

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5. การใช้ Underscore.js _.cloneลิงก์Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

แต่ผิดเมื่อ

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH การเปรียบเทียบสมรรถนะสนามเด็กเล่น 1 ~ 3 http://jsben.ch/KVQLd ประสิทธิภาพการคัดลอกวัตถุใน JavaScript


5
Object.assign()ไม่ได้ทำสำเนาลึก
Roymunson

1
คุณควรเพิ่มมาตรฐานสำหรับสิ่งเหล่านี้ ที่จะเป็นประโยชน์มาก
jcollum

เมื่อฉันใช้ "วิธีการที่สร้างขึ้น" บนวัตถุที่มีอาร์เรย์ที่ฉันไม่สามารถใช้ pop () หรือ splice () กับมันฉันไม่เข้าใจว่าทำไม let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();มันโยน: TypeError: tmp.title.pop is not a function(แน่นอนป๊อป () ทำงานได้ดีถ้าฉันเพียงdo let tmp = dataแต่จากนั้นฉันไม่สามารถแก้ไข tmp โดยไม่ส่งผลกระทบต่อข้อมูล)
24919

เฮ้ตัวอย่างสุดท้ายของคุณผิด ในความคิดของฉันคุณต้องใช้ _clone ไม่ใช่ _cloneDeep สำหรับตัวอย่างที่ผิด
kenanyildiz

วิธีการที่สร้างขึ้นนี้ (2. ) ไม่สามารถใช้กับอาร์เรย์ได้หรือไม่
Toivo Säwén

57

มีห้องสมุด (เรียกว่า "โคลน")ซึ่งทำได้ค่อนข้างดี มันมีการโคลนซ้ำ / คัดลอกวัตถุโดยพลการที่สมบูรณ์ที่สุดที่ฉันรู้ นอกจากนี้ยังรองรับการอ้างอิงแบบวงกลมซึ่งยังไม่ครอบคลุมโดยคำตอบอื่น ๆ

คุณสามารถค้นหาได้ในเวลา 23.00น. เช่นกัน มันสามารถใช้สำหรับเบราว์เซอร์เช่นเดียวกับ Node.js

นี่คือตัวอย่างวิธีการใช้งาน:

ติดตั้งด้วย

npm install clone

หรือแพคเกจกับเอนเดอร์

ender build clone [...]

คุณสามารถดาวน์โหลดซอร์สโค้ดด้วยตนเอง

จากนั้นคุณสามารถใช้มันในซอร์สโค้ดของคุณ

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้แต่งห้องสมุด)


3
โคลน npm ได้รับการประเมินค่าให้ฉันสำหรับการโคลนวัตถุที่ซ้อนกันโดยพลการ นี่คือคำตอบที่ถูกต้อง
Andy Ray

ประสิทธิภาพของ lib ของคุณเทียบกับสมมุติว่าJSON.parse(JSON.stringify(obj))อย่างไร?
pkyeck

นี่คือไลบรารีที่ระบุว่ามีตัวเลือกที่เร็วกว่า ยังไม่ได้ทดสอบ
pvorb

วิธีแก้ปัญหาที่ดีและสิ่งนี้รองรับการอ้างอิงแบบวงกลม (ไม่เหมือนกับการแยก JSON)
ลุค

55

Cloning วัตถุนั้นเป็นเรื่องที่น่ากังวลเสมอใน JS แต่มันเป็นเรื่องเกี่ยวกับ ES6 ก่อนหน้านี้ฉันทำรายการวิธีที่แตกต่างของการคัดลอกวัตถุใน JavaScript ด้านล่างจินตนาการว่าคุณมี Object ด้านล่างและต้องการสำเนาที่ลึก:

var obj = {a:1, b:2, c:3, d:4};

การคัดลอกวัตถุนี้มีวิธีไม่กี่วิธีโดยไม่ต้องเปลี่ยนจุดเริ่มต้น:

1) ES5 +, ใช้ฟังก์ชั่นง่ายๆในการทำสำเนาสำหรับคุณ:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 + ใช้ JSON.parse และ JSON.stringify

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs & Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

หวังว่าความช่วยเหลือเหล่านี้ ...


2
การโคลนในเครื่องหมายขีดล่างไม่ใช่การโคลนแบบลึกในเวอร์ชันปัจจุบัน
Rogelio

ขอบคุณ ใช่เป็นเอกสารใหม่สำหรับ Underscore ... clone_.clone (object) สร้างโคลนที่คัดลอกตื้นของวัตถุธรรมดาที่ให้ไว้ วัตถุหรืออาร์เรย์ที่ซ้อนกันใด ๆ จะถูกคัดลอกโดยการอ้างอิงไม่ใช่การทำซ้ำ _.clone ({ชื่อ: 'moe'}); => {ชื่อ: 'moe'};
Alireza

59
Object.assignไม่ได้ลึกคัดลอก ตัวอย่าง: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". ถ้านี้คือสำเนาลึกy.a.bยังคงเป็นแต่ก็ตอนนี้c d
kba

8
Object.assign () จะโคลนคุณสมบัติระดับแรกเท่านั้น!
haemse

5
ฟังก์ชัน cloneSO () คืออะไร
pastorello

53

ฉันรู้ว่านี่เป็นโพสต์เก่า แต่ฉันคิดว่านี่อาจเป็นประโยชน์กับคนต่อไปที่สะดุด

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

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

16
คำตอบนี้ไม่เกี่ยวข้องกันจริงๆเพราะคำถามคือ: ตัวอย่างที่ได้รับ b จะสร้างสำเนาได้อย่างไรในขณะที่ไม่รู้เกี่ยวกับโรงงาน a หรือไม่ต้องการใช้โรงงาน เหตุผลหนึ่งที่อาจไม่ต้องการใช้โรงงานก็คือหลังจากการเริ่มอินสแตนซ์ b อาจเริ่มต้นด้วยข้อมูลเพิ่มเติม (เช่นอินพุตของผู้ใช้)
Noel Abrahams

12
มันเป็นความจริงที่นี่ไม่ใช่คำตอบสำหรับคำถาม แต่ฉันคิดว่ามันสำคัญที่จะต้องอยู่ที่นี่เพราะเป็นคำตอบของคำถามที่ฉันสงสัยว่าผู้คนจำนวนมากที่มาที่นี่มีความหมายที่จะถามจริงๆ
Semicolon

8
ขอโทษนะฉันไม่เข้าใจว่าทำไม upvotes มากมาย การโคลนวัตถุเป็นแนวคิดที่ชัดเจนคุณวางกรวยวัตถุจากวัตถุอีกอันหนึ่งและมันไม่มีอะไรเกี่ยวข้องกับการสร้างวัตถุใหม่ด้วยลวดลายโรงงาน
opensas

2
ในขณะที่ใช้งานได้กับวัตถุที่กำหนดไว้ล่วงหน้า "การโคลน" ในลักษณะนี้จะไม่รู้จักคุณสมบัติใหม่ที่เพิ่มไปยังวัตถุต้นฉบับ หากคุณสร้าง a ให้เพิ่มคุณสมบัติใหม่ไปที่ a จากนั้นสร้าง b b จะไม่มีคุณสมบัติใหม่ โดยพื้นฐานแล้วรูปแบบของโรงงานจะไม่เปลี่ยนแปลงกับคุณสมบัติใหม่ นี่ไม่ใช่การโคลนนิ่งกระบวนทัศน์ ดู: jsfiddle.net/jzumbrun/42xejnbx
Jon

1
ฉันคิดว่านี่เป็นคำแนะนำที่ดีโดยทั่วไปแทนที่จะใช้const defaultFoo = { a: { b: 123 } };คุณสามารถไปได้const defaultFoo = () => ({ a: { b: 123 } };และปัญหาของคุณจะได้รับการแก้ไข อย่างไรก็ตามมันไม่ได้เป็นคำตอบสำหรับคำถาม มันอาจจะสมเหตุสมผลมากกว่าในการแสดงความคิดเห็นต่อคำถามไม่ใช่คำตอบเต็มรูปแบบ
Josh จาก Qaribou

48

หากคุณกำลังใช้งานไลบรารีUnderscore.jsมีวิธีการโคลน

var newObject = _.clone(oldObject);

24
lodash มีวิธี cloneDeep นอกจากนี้ยังสนับสนุน param อื่นเพื่อโคลนให้ลึก: lodash.com/docs#cloneและlodash.com/docs#cloneDeep
opensas

12
@opensas เห็นด้วย Lodash โดยทั่วไปจะดีกว่าขีดเส้นใต้
nha

7
ฉันสนับสนุนการลบคำตอบนี้และคำตอบอื่น ๆ ทั้งหมดซึ่งเป็นเพียงการอ้างอิงบรรทัดเดียวกับ.clone(...)วิธีการของยูทิลิตี้ไลบรารี ห้องสมุดหลักทุกแห่งจะมีห้องสมุดเหล่านั้นและคำตอบที่ไม่มีรายละเอียดสั้น ๆ ซ้ำ ๆ ไม่มีประโยชน์สำหรับผู้เยี่ยมชมส่วนใหญ่ที่ไม่ได้ใช้ห้องสมุดเฉพาะนั้น
Jeremy Banks

41

นี่คือเวอร์ชันของคำตอบของ ConroyP ด้านบนที่ใช้งานได้แม้ว่าตัวสร้างมีพารามิเตอร์ที่ต้องการ:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

ฟังก์ชั่นนี้ยังมีอยู่ในห้องสมุดsimpleooของฉัน

แก้ไข:

นี่เป็นรุ่นที่แข็งแกร่งกว่านี้ (ขอบคุณ Justin McCandless ซึ่งตอนนี้รองรับการอ้างอิงแบบวนด้วย):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

30

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

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

มีอะไรผิดปกติกับคำตอบนี้หรือไม่? มันมีประโยชน์มากกว่าในการแก้ปัญหาแบบสแตนด์อโลน แต่ใช้งานง่าย แต่โซลูชัน jQuery เป็นที่นิยมมากกว่า ทำไมถึงเป็นอย่างนั้น?
ceremcem

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

4
สำหรับวัตถุที่เรียบง่ายนี่เป็น Chrome ที่ช้ากว่าคำตอบที่ให้มาประมาณ 6 เท่าและช้าลงมากเมื่อความซับซ้อนของวัตถุเพิ่มขึ้น มันมีขนาดใหญ่มากและสามารถบรรจุแอปพลิเคชันของคุณได้อย่างรวดเร็ว
tic

1
คุณไม่ต้องการข้อมูล แต่เป็นความเข้าใจในสิ่งที่เกิดขึ้น เทคนิคการโคลนนี้ทำให้วัตถุทั้งหมดเป็นสตริงแล้วแยกวิเคราะห์สตริงอนุกรมนั้นเพื่อสร้างวัตถุ โดยเนื้อแท้แล้วมันจะช้ากว่าการจัดเรียงหน่วยความจำบางส่วนอีกครั้ง แต่ด้วยสิ่งที่กล่าวมาสำหรับโครงการขนาดเล็กถึงขนาดกลาง (ขึ้นอยู่กับคำจำกัดความของคุณของ "ขนาดกลาง") ที่ใส่ใจว่ามันมีประสิทธิภาพน้อยกว่า 1,000 เท่าหรือไม่ หากวัตถุของคุณมีขนาดเล็กและคุณไม่ได้โคลนพวกมันเป็นตัน 1,000x ซึ่งแทบไม่มีอะไรเลย
machineghost

3
นอกจากนี้วิธีนี้จะสูญเสียวิธีการ (หรือสิ่งใด ๆ ที่ไม่ได้รับอนุญาตใน JSON) บวก - JSON.stringify จะแปลงวัตถุวันที่เป็นสตริง ... และไม่ใช่วิธีอื่น ๆ ;) ออกจากโซลูชันนี้
Mr MT

22

Crockford แนะนำ (และฉันชอบ) โดยใช้ฟังก์ชั่นนี้:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var newObject = object(oldObject);

มันสั้นทำงานได้ตามที่คาดหวังและคุณไม่ต้องการห้องสมุด


แก้ไข:

นี่คือ polyfill สำหรับObject.createคุณจึงสามารถใช้สิ่งนี้ได้

var newObject = Object.create(oldObject);

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

สำหรับตัวอย่างถ้า oldObject.a = 5;

newObject.a; // is 5

แต่:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

9
ถูกต้องฉันถ้าฉันผิด แต่นั่นไม่ใช่หน้าที่ beget ของ Crockford สำหรับมรดกต้นแบบ? มันใช้กับโคลนได้อย่างไร?
Alex Nolasco

3
ใช่ฉันกลัวการอภิปรายนี้: อะไรคือความแตกต่างในทางปฏิบัติระหว่างการลอกเลียนแบบการคัดลอกและการสืบทอดต้นแบบเมื่อคุณควรใช้แต่ละฟังก์ชันที่หน้าที่ในหน้านี้กำลังทำอะไร ฉันพบหน้า SO นี้โดย googling "javascript copy object" สิ่งที่ฉันกำลังมองหาคือฟังก์ชั่นด้านบนดังนั้นฉันจึงกลับมาแชร์อีกครั้ง ฉันเดาว่าผู้ถามก็กำลังมองหาสิ่งนี้เช่นกัน
Chris Broski

51
ความแตกต่างระหว่างการโคลน / การคัดลอกและการสืบทอดคือ - โดยใช้ตัวอย่างของคุณเมื่อฉันเปลี่ยนคุณสมบัติของ oldObject คุณสมบัติก็จะเปลี่ยนใน newObject หากคุณทำสำเนาคุณสามารถทำสิ่งที่คุณต้องการด้วย oldObject โดยไม่เปลี่ยน newObject
Ridcully

13
การทำเช่นนี้จะเป็นการทำลายการตรวจสอบ hasOwnProperty ดังนั้นจึงเป็นวิธีที่ค่อนข้างแฮ็กในการโคลนวัตถุและจะให้ผลลัพธ์ที่ไม่คาดคิดแก่คุณ
Corban Brook

var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Cody

22

Lodash มีเมธอด _.cloneDeep (value) ที่ดี :

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

5
ฉันสนับสนุนการลบคำตอบนี้และคำตอบอื่น ๆ ทั้งหมดซึ่งเป็นเพียงการอ้างอิงบรรทัดเดียวกับ.clone(...)วิธีการของยูทิลิตี้ไลบรารี ห้องสมุดหลักทุกแห่งจะมีห้องสมุดเหล่านั้นและคำตอบที่ไม่มีรายละเอียดสั้น ๆ ซ้ำ ๆ ไม่มีประโยชน์สำหรับผู้เยี่ยมชมส่วนใหญ่ที่ไม่ได้ใช้ห้องสมุดเฉพาะนั้น
Jeremy Banks

_.merge({}, objA)วิธีที่ง่ายกว่าคือการใช้งาน หาก lodash ไม่ได้ทำการเปลี่ยนวัตถุในครั้งแรกcloneฟังก์ชันนั้นก็ไม่จำเป็น
Rebs

7
Google ค้นหาการโคลนวัตถุ JS อ้างอิงที่นี่ ฉันใช้ Lodash ดังนั้นคำตอบนี้จึงเกี่ยวข้องกับฉัน อย่าไป "ผู้ลบวิกิพีเดียทั้งหมด" ในคำตอบโปรด
Rebs

2
ในโหนด 9 JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) นั้นเร็วกว่า _.deepClone (arrayOfAbout5KFlatObjects) จำนวนมาก
Dan Dascalescu

21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

17
ปัญหาเกี่ยวกับวิธีการที่ถ้าคุณมีวัตถุย่อยภายใน obj การอ้างอิงของพวกเขาจะถูกโคลนและไม่ใช่ค่าของทุกวัตถุย่อย
Kamarey

1
เพียงแค่ทำให้มันวนซ้ำเพื่อให้วัตถุย่อยถูกโคลนอย่างลึกซึ้ง
fiatjaf

แค่อยากรู้อยากเห็น ... จะไม่เป็นตัวแปรโคลนจะมีตัวชี้ไปยังคุณสมบัติของวัตถุต้นฉบับหรือไม่ เพราะดูเหมือนว่าจะไม่มีการจัดสรรหน่วยความจำใหม่
Rupesh Patel

3
ใช่. นี่เป็นเพียงสำเนาตื้น ๆ ดังนั้นโคลนจะชี้ไปที่วัตถุเดียวกันที่ชี้ไปยังวัตถุดั้งเดิม
Mark Cidade

นี่ไม่ใช่คำตอบ คุณเพียงแค่บรรจุวัตถุโดยอ้างอิงถึงวัตถุอื่น การเปลี่ยนแปลงวัตถุต้นฉบับจะทำการเปลี่ยนแปลง "โคลน"
Shawn Whinnery

19

ตื้นคัดลอกหนึ่งซับ ( ECMAScript รุ่นที่ 5 ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

และคัดลอกหนึ่งซับ ( ECMAScript 6th edition , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

6
สิ่งนี้อาจใช้ได้สำหรับวัตถุธรรมดา แต่คัดลอกเฉพาะค่าคุณสมบัติ มันไม่ได้สัมผัสกับโซ่ต้นแบบและโดยการใช้Object.keysมันข้ามคุณสมบัติที่ไม่นับและสืบทอด นอกจากนี้มันยังสูญเสียคุณสมบัติอธิบายโดยทำการกำหนดโดยตรง
Matt Bierner

หากคุณคัดลอกต้นแบบเช่นกันคุณจะหายไปเฉพาะตัวนับที่ไม่นับและตัวอธิบายคุณสมบัติใช่หรือไม่ ใช้ได้ดีทีเดียว. :)
sam

ประสิทธิภาพการทำงานนอกเหนือจากนี้เป็นวิธีที่สะดวกในการคัดลอกวัตถุตื้น ฉันมักจะใช้สิ่งนี้เพื่อจัดเรียงคุณสมบัติที่พักปลอมในการมอบหมายการทำลายล้างในองค์ประกอบของฉัน
mjohnsonengr

17

เพียงเพราะฉันไม่เห็นAngularJSพูดถึงและคิดว่าผู้คนอาจต้องการทราบ ...

angular.copy ยังมีวิธีการคัดลอกวัตถุและอาร์เรย์ลึก


หรืออาจจะใช้วิธีเดียวกับ jQiery ขยาย:angular.extend({},obj);
Galvani

2
@Galvani: มันควรจะตั้งข้อสังเกตว่าjQuery.extendและangular.extendทั้งสองสำเนาตื้น angular.copyเป็นสำเนาลึก
Dan Atkinson

16

ดูเหมือนว่าจะไม่มีโอเปอเรเตอร์โคลนนิ่งในอุดมคติสำหรับวัตถุที่มีลักษณะคล้ายอาร์เรย์ ตามรหัสด้านล่างแสดงให้เห็นว่า jQuery cloner ของ John Resig เปลี่ยนอาร์เรย์ที่มีคุณสมบัติที่ไม่ใช่ตัวเลขเป็นวัตถุที่ไม่ใช่อาร์เรย์และ cloner JSON ของ RegDwight ลดคุณสมบัติที่ไม่ใช่ตัวเลข การทดสอบต่อไปนี้แสดงให้เห็นถึงจุดเหล่านี้ในหลายเบราว์เซอร์:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

14
ตามที่คนอื่น ๆ ได้ชี้ให้เห็นในความเห็นต่อคำตอบของ Resig ถ้าคุณต้องการโคลนวัตถุแบบ array คุณเปลี่ยน {} เป็น [] ในการขยายการโทรเช่น jQuery.extend (จริง, [], obj)
Anentropic

15

ฉันมีสองคำตอบที่ดีขึ้นอยู่กับว่าวัตถุประสงค์ของคุณคือการโคลน "วัตถุ JavaScript เก่าธรรมดา" หรือไม่

สมมติว่าความตั้งใจของคุณคือการสร้างโคลนที่สมบูรณ์โดยไม่มีการอ้างอิงต้นแบบกลับไปยังวัตถุต้นฉบับ หากคุณไม่สนใจโคลนที่สมบูรณ์คุณสามารถใช้รูทีน Object.clone () จำนวนมากที่ให้ไว้ในคำตอบอื่น ๆ (รูปแบบของ Crockford)

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

var clone = JSON.parse(JSON.stringify(obj));

โปรดทราบว่าวัตถุต้นฉบับจะต้องเป็นวัตถุ JSON บริสุทธิ์ นี่คือคุณสมบัติทั้งหมดที่ซ้อนกันของมันต้องเป็นสเกลาร์ (เช่นบูลีนสตริงอาร์เรย์วัตถุ ฯลฯ ) ฟังก์ชันหรือวัตถุพิเศษเช่น RegExp หรือ Date จะไม่ถูกโคลน

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

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

ตอนนี้สำหรับวัตถุ JavaScript ที่ไม่ธรรมดาไม่มีคำตอบที่ง่ายจริงๆ ในความเป็นจริงมันไม่สามารถเกิดขึ้นได้เพราะธรรมชาติของฟังก์ชั่น JavaScript และสถานะวัตถุภายใน การโคลนโครงสร้าง JSON แบบลึกพร้อมกับฟังก์ชั่นด้านในคุณต้องสร้างฟังก์ชันเหล่านั้นและบริบทภายใน และจาวาสคริปต์ก็ไม่มีวิธีที่เป็นมาตรฐานในการทำเช่นนั้น

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

เราเขียนขึ้นเอง แต่แนวทางทั่วไปที่ดีที่สุดที่ฉันเคยเห็นครอบคลุมอยู่ที่นี่:

http://davidwalsh.name/javascript-clone

นี่คือความคิดที่ถูกต้อง ผู้เขียน (David Walsh) ได้แสดงความคิดเห็นการโคลนของฟังก์ชั่นทั่วไป นี่คือสิ่งที่คุณอาจเลือกที่จะทำขึ้นอยู่กับกรณีการใช้งานของคุณ

แนวคิดหลักคือคุณต้องจัดการพิเศษในการสร้างอินสแตนซ์ของฟังก์ชั่นของคุณ (หรือคลาสต้นแบบเพื่อพูด) ในแต่ละประเภท ที่นี่เขามีตัวอย่างสองสามตัวอย่างสำหรับ RegExp และ Date

ไม่เพียง แต่เป็นรหัสสั้น ๆ นี้ แต่ก็ยังอ่านได้มาก มันง่ายต่อการขยาย

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

ดังนั้นคุณไป สองแนวทาง ทั้งสองมีประสิทธิภาพในมุมมองของฉัน


13

นี่ไม่ใช่วิธีการแก้ปัญหาที่มีประสิทธิภาพมากที่สุด กรณีทดสอบง่ายๆด้านล่าง ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

การทดสอบอาร์เรย์เป็นวง ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

ทดสอบฟังก์ชั่น ...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false


11

ผมไม่เห็นด้วยกับคำตอบที่มีคะแนนโหวตมากที่สุดที่นี่ ซ้ำลึกโคลนเป็นได้เร็วขึ้นมากกว่าJSON.parse (JSON.stringify (obj))วิธีการที่กล่าวถึง

  • Jsperfจัดอันดับเป็นอันดับหนึ่งที่นี่: https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
  • Jsbenจากคำตอบข้างต้นได้รับการอัพเดตเพื่อแสดงให้เห็นว่าการโคลนนิ่งแบบวนซ้ำแบบซ้ำ ๆ นั้นเต้นไปตามที่คนอื่น ๆ พูดถึง: http://jsben.ch/13YKQ

และนี่คือฟังก์ชันสำหรับการอ้างอิงอย่างรวดเร็ว:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}

2
ฉันชอบวิธีนี้ แต่ไม่สามารถจัดการวันที่ได้อย่างถูกต้อง ลองเพิ่มบางอย่างเช่นif(o instanceof Date) return new Date(o.valueOf());หลังจากตรวจสอบค่าว่าง `
ลูอิส

เกิดปัญหากับการอ้างอิงแบบวงกลม
Harry

ใน Firefox เสถียรล่าสุดนี่เป็นวิธีที่ยาวกว่ากลยุทธ์อื่น ๆ ที่ลิงก์ Jsben.ch ตามลำดับความสำคัญหรือมากกว่า มันเต้นคนอื่นในทิศทางที่ผิด
WBT

11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

10

เมื่อคุณสามารถใช้ECMAScript 6หรือtranspilersได้เท่านั้น

คุณสมบัติ:

  • จะไม่ทริกเกอร์ทะลวง / setter ในขณะที่คัดลอก
  • รักษาทะเยอทะยาน / setter
  • รักษาข้อมูลต้นแบบ
  • ทำงานกับสไตล์การเขียนOO เชิงวัตถุและฟังก์ชัน

รหัส:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}

9

นี่คือวิธีการโคลนนิ่ง () ที่สามารถโคลนวัตถุ JavaScript ใด ๆ มันจัดการเกือบทุกกรณี:

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;
};

มันแปลงดั้งเดิมเป็นวัตถุห่อหุ้มไม่ใช่ทางออกที่ดีในกรณีส่วนใหญ่
Danubian Sailor

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