วิธีการโคลนอินสแตนซ์คลาสจาวาสคริปต์ ES6


96

ฉันจะโคลนอินสแตนซ์คลาส Javascript โดยใช้ ES6 ได้อย่างไร

ฉันไม่สนใจโซลูชันที่ใช้ jquery หรือ $ expand

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

แก้ไข: มีการแนะนำว่าคำถามของฉันซ้ำกัน ฉันเห็นคำตอบนั้น แต่มันมีอายุ 7 ปีและเกี่ยวข้องกับคำตอบที่ซับซ้อนมากโดยใช้ pre-ES6 js ฉันขอแนะนำว่าคำถามของฉันซึ่งช่วยให้ ES6 มีวิธีแก้ปัญหาที่ง่ายกว่ามาก


2
หากคุณมีคำตอบใหม่สำหรับคำถามเก่าใน Stack Overflow โปรดเพิ่มคำตอบนั้นสำหรับคำถามเดิมอย่าเพิ่งสร้างคำถามใหม่
ลิงนอกรีต

1
ฉันเห็นปัญหาที่ Tom กำลังเผชิญอยู่เนื่องจากอินสแตนซ์คลาส ES6 ทำงานแตกต่างจาก Objects "ปกติ"
CherryNerd

2
นอกจากนี้โค้ดชิ้นแรกในคำตอบที่ยอมรับว่า "สามารถทำซ้ำได้" ของคุณมีข้อขัดข้องจริงเมื่อฉันพยายามเรียกใช้งานผ่านอินสแตนซ์ของคลาส ES6
CherryNerd

ฉันคิดว่านี่ไม่ใช่สิ่งที่ซ้ำกันเพราะแม้ว่าอินสแตนซ์คลาส ES6 จะเป็นอ็อบเจ็กต์ แต่ไม่ใช่ทุกอ็อบเจ็กต์จะเป็นอินสแตนซ์คลาส ES6 ดังนั้นคำถามอื่น ๆ จึงไม่สามารถตอบปัญหาของคำถามนี้ได้
Tomáš Zato - คืนสถานะ Monica

5
ไม่ซ้ำกัน คำถามอื่น ๆ เกี่ยวกับบริสุทธิ์Objectที่ใช้เป็นผู้ถือข้อมูล อันนี้เกี่ยวกับ ES6 classes และปัญหาที่จะไม่สูญเสียข้อมูลประเภทคลาส มันต้องการทางออกที่แตกต่าง
flori

คำตอบ:


111

มันซับซ้อน; ฉันพยายามมาก! ในท้ายที่สุดซับเดียวนี้ใช้ได้กับอินสแตนซ์คลาส ES6 ที่กำหนดเองของฉัน:

let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)

หลีกเลี่ยงการตั้งค่าต้นแบบเพราะพวกเขาบอกว่ามันทำให้โค้ดช้าลงมาก

สนับสนุนสัญลักษณ์ แต่ไม่เหมาะสำหรับ getters / setters และไม่ทำงานกับคุณสมบัติที่ไม่สามารถนับได้ (ดูเอกสาร Object.assign () ) นอกจากนี้การโคลนคลาสภายในพื้นฐาน (เช่น Array, Date, RegExp, Map และอื่น ๆ ) ดูเหมือนว่าจะต้องมีการจัดการแบบแยกส่วน

สรุป: มันเป็นระเบียบ หวังว่าสักวันจะมีฟังก์ชั่นการโคลนแบบเนทีฟและสะอาด


1
วิธีนี้จะไม่คัดลอกวิธีการแบบคงที่เนื่องจากไม่สามารถระบุคุณสมบัติของตัวเองได้
คุณลาวาลัมป์

5
@ Mr.Lavalamp และคุณจะคัดลอก (ยัง) วิธีการคงที่ได้อย่างไร?
flori

สิ่งนี้จะทำลายอาร์เรย์! มันจะแปลงอาร์เรย์ทั้งหมดเป็นวัตถุด้วยปุ่ม "0", "1", ...
Vahid

1
@KeshaAntonov คุณอาจสามารถหาวิธีแก้ปัญหาด้วยวิธี typeof และ Array ตัวฉันเองชอบที่จะโคลนคุณสมบัติทั้งหมดด้วยตนเอง
Vahid

1
อย่าคาดหวังว่ามันจะโคลนคุณสมบัติที่เป็นวัตถุตัวเอง: jsbin.com/qeziwetexu/edit?js,console
jduhls

10
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

สังเกตลักษณะของObject.assign : ทำสำเนาตื้นและไม่คัดลอกเมธอดคลาส

หากคุณต้องการสำเนาลึกหรือการควบคุมที่มากกว่าการคัดลอกแล้วมีฟังก์ชั่น lodash โคลน


2
ตั้งแต่สร้างวัตถุใหม่ที่มีต้นแบบที่ระบุไว้ทำไมไม่เพียงแค่นั้นObject.create const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah)นอกจากนี้วิธีการเรียนจะถูกคัดลอกด้วย
barbatus

1
@barbatus ที่ใช้ต้นแบบที่ไม่ถูกต้องแม้ว่าBlah.prototype != instanceOfBlah. คุณควรใช้Object.getPrototypeOf(instanceOfBlah)
Bergi

1
@Bergi ไม่อินสแตนซ์คลาส ES6 ไม่มีต้นแบบเสมอไป ตรวจสอบcodepen.io/techniq/pen/qdZeZmที่ใช้งานได้กับอินสแตนซ์ด้วย
barbatus

@barbatus ขอโทษอะไรนะ? ฉันไม่ทำตาม อินสแตนซ์ทั้งหมดมีต้นแบบนั่นคือสิ่งที่ทำให้อินสแตนซ์เป็นอินสแตนซ์ ลองใช้รหัสจากคำตอบของ flori
Bergi

1
@Bergi ฉันคิดว่ามันขึ้นอยู่กับการกำหนดค่า Babel หรืออะไรบางอย่าง ตอนนี้ฉันกำลังใช้แอปเนทีฟแบบรีแอคทีฟและอินสแตนซ์ที่ไม่มีคุณสมบัติที่สืบทอดมาจะมีโมฆะต้นแบบอยู่ที่นั่น อย่างที่คุณเห็นที่นี่developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… เป็นไปได้ว่า getPrototypeOf จะคืนค่า null
barbatus

3

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

ในการโคลนวัตถุใน JavaScript ไม่มีวิธีที่ง่ายหรือตรงไปตรงมา นี่คือตัวอย่างแรกที่ใช้ "Shallow Copy":

1 -> โคลนตื้น:

class Employee {
    constructor(first, last, street) {
        this.firstName = first;
        this.lastName = last;
        this.address = { street: street };
    }

    logFullName() {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

let original = new Employee('Cassio', 'Seffrin', 'Street A, 23');
let clone =  Object.assign({},original); //object.assing() method
let cloneWithPrototype Object.create(Object.getPrototypeOf(original)), original) //  the clone will inherit the prototype methods of the original.
let clone2 = { ...original }; // the same of object assign but shorter sintax using "spread operator"
clone.firstName = 'John';
clone.address.street = 'Street B, 99'; //will not be cloned

ผล:

original.logFullName ():

ผลลัพธ์: Cassio Seffrin

clone.logFullName ():

ผลลัพธ์: John Seffrin

original.address.street;

ผลลัพธ์: 'Street B, 99' // สังเกตว่าวัตถุย่อยดั้งเดิมมีการเปลี่ยนแปลง

หมายเหตุ: หากอินสแตนซ์มีการปิดเป็นคุณสมบัติของตัวเองวิธีนี้จะไม่รวมเข้าด้วยกัน ( อ่านเพิ่มเติมเกี่ยวกับการปิด ) และนอกจากนี้วัตถุย่อย "ที่อยู่" จะไม่ถูกโคลน

clone.logFullName ()

จะไม่ทำงาน.

cloneWithPrototype.logFullName ()

จะใช้งานได้เพราะโคลนจะคัดลอก Prototypes ไปด้วย

ในการโคลนอาร์เรย์ด้วย Object.assign:

let cloneArr = array.map((a) => Object.assign({}, a));

โคลนอาร์เรย์โดยใช้ ECMAScript spread sintax:

let cloneArrSpread = array.map((a) => ({ ...a }));

2 -> โคลนลึก:

ในการเก็บการอ้างอิงอ็อบเจ็กต์ใหม่ทั้งหมดเราสามารถใช้ JSON.stringify () เพื่อแยกวิเคราะห์ออบเจ็กต์ดั้งเดิมเป็นสตริงและหลังจากแยกวิเคราะห์กลับเป็น JSON.parse ()

let deepClone = JSON.parse(JSON.stringify(original));

ด้วยการโคลนลึกการอ้างอิงไปยังที่อยู่จะถูกเก็บไว้ อย่างไรก็ตาม deepClone Prototypes จะหายไปดังนั้น deepClone.logFullName () จะไม่ทำงาน

3 -> ห้องสมุดบุคคลที่ 3:

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

ขีดล่าง: ให้ cloneUnderscore = _ (ต้นฉบับ) .clone ();

Loadash clone: ​​var cloneLodash = _.cloneDeep (ต้นฉบับ);

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


1
เมื่อกำหนดให้{}โคลนจะไม่สืบทอดเมธอดต้นแบบใด ๆ ของต้นฉบับ clone.logFullName()จะไม่ทำงานเลย ที่Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)คุณเคยมีมาก่อนนั้นสบายดีทำไมคุณถึงเปลี่ยนไป
Bergi

1
@Bergi ขอบคุณสำหรับการมีส่วนร่วมของคุณฉันกำลังแก้ไขคำตอบของฉันตอนนี้ฉันเพิ่มประเด็นของคุณเพื่อคัดลอกต้นแบบ!
Cassio Seffrin

1
ฉันขอขอบคุณสำหรับความช่วยเหลือของคุณ @Bergi โปรดแสดงความคิดเห็นตอนนี้ ฉบับฉันหมดแล้ว ฉันคิดว่าตอนนี้คำตอบครอบคลุมคำถามเกือบทั้งหมดแล้ว ขอบคุณ!
Cassio Seffrin

1
ใช่และเช่นเดียวกับObject.assign({},original)มันไม่ทำงาน
Bergi

1
บางครั้งแนวทางที่ง่ายกว่าก็คือทั้งหมดที่เราต้องการ หากคุณไม่ต้องการ Prototypes และวัตถุที่ซับซ้อนเพียงแค่ "clone = {... original}" ก็สามารถแก้ปัญหาได้
Cassio Seffrin

0

อีกหนึ่งซับ:

เวลาส่วนใหญ่ ... (ใช้ได้กับ Date, RegExp, Map, String, Number, Array), btw, cloning string, number เป็นเรื่องตลกเล็กน้อย

let clone = new obj.constructor(...[obj].flat())

สำหรับคลาสเหล่านั้นที่ไม่มีตัวสร้างการคัดลอก:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)

0
class A {
  constructor() {
    this.x = 1;
  }

  y() {
    return 1;
  }
}

const a = new A();

const output = Object.getOwnPropertyNames(Object.getPrototypeOf(a)).concat(Object.getOwnPropertyNames(a)).reduce((accumulator, currentValue, currentIndex, array) => {
  accumulator[currentValue] = a[currentValue];
  return accumulator;
}, {});

ป้อนคำอธิบายภาพที่นี่


-4

คุณสามารถใช้ตัวดำเนินการกระจายตัวอย่างเช่นหากคุณต้องการโคลนวัตถุชื่อ Obj:

let clone = { ...obj};

และถ้าคุณต้องการเปลี่ยนแปลงหรือเพิ่มอะไรให้กับวัตถุที่ถูกโคลน:

let clone = { ...obj, change: "something" };
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.