Cara menggandakan instance kelas ES6 javascript

96

Bagaimana cara mengkloning instance kelas Javascript menggunakan ES6.

Saya tidak tertarik dengan solusi berdasarkan jquery atau $ extends.

Saya telah melihat diskusi yang cukup lama tentang kloning objek yang menunjukkan bahwa masalahnya cukup rumit, tetapi dengan ES6 solusi yang sangat sederhana muncul dengan sendirinya - saya akan meletakkannya di bawah dan melihat apakah orang menganggapnya memuaskan.

edit: disarankan bahwa pertanyaan saya adalah duplikat; Saya melihat jawaban itu tetapi usianya 7 tahun dan melibatkan jawaban yang sangat rumit menggunakan pra-ES6 js. Saya menyarankan bahwa pertanyaan saya, yang memungkinkan untuk ES6, memiliki solusi yang jauh lebih sederhana.

Tom
sumber
2
Jika Anda memiliki jawaban baru untuk pertanyaan lama tentang Stack Overflow, harap tambahkan jawaban tersebut ke pertanyaan awal, jangan hanya membuat yang baru.
Heretic Monkey
1
Saya melihat masalah yang Tom hadapi karena instance kelas ES6 bekerja berbeda dari Objek "biasa".
CherryNerd
2
Selain itu, potongan kode pertama dalam jawaban yang diterima "kemungkinan duplikat" Anda benar-benar macet ketika saya mencoba menjalankannya di atas instance kelas
ES6
Saya pikir ini bukan duplikat, karena meskipun instance kelas ES6 adalah objek, tidak setiap objek adalah instance kelas ES6 dan oleh karena itu pertanyaan lain tidak membahas masalah pertanyaan ini.
Tomáš Zato - Kembalikan Monica
5
Ini bukan duplikat. Pertanyaan lainnya adalah tentang murni Objectyang digunakan sebagai pemegang data. Yang satu ini tentang ES6 classes dan masalah untuk tidak kehilangan informasi jenis kelas. Ini membutuhkan solusi yang berbeda.
flori

Jawaban:

111

Itu rumit; Saya sudah mencoba banyak! Pada akhirnya, satu baris ini berfungsi untuk instance kelas ES6 khusus saya:

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

Ini menghindari pengaturan prototipe karena mereka mengatakan itu sangat memperlambat kode.

Ini mendukung simbol tetapi tidak sempurna untuk getter / setter dan tidak bekerja dengan properti non-enumerable (lihat Object.assign () docs ). Juga, kloning kelas internal dasar (seperti Array, Date, RegExp, Map, dll.) Sayangnya sering kali membutuhkan penanganan individu.

Kesimpulan: Ini berantakan. Mari berharap suatu hari akan ada fungsi klon asli dan bersih.

flori
sumber
1
Ini tidak akan menyalin metode statis karena mereka sebenarnya bukan properti sendiri yang dapat dihitung.
Tn. Lavalamp
5
@ Mr.Lavalamp dan bagaimana Anda dapat menyalin (juga) metode statis?
flori
ini akan menghancurkan array! Ini akan mengubah semua larik menjadi objek dengan tombol "0", "1", ....
Vahid
1
@KeshaAnov Anda mungkin dapat menemukan solusi dengan metode typeof dan Array. Saya sendiri lebih suka mengkloning semua properti secara manual.
Vahid
1
Jangan berharap untuk mengkloning properti yang merupakan objek itu sendiri: jsbin.com/qeziwetexu/edit?js,console
jduhls
10
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Perhatikan karakteristik Object.assign : ia melakukan penyalinan dangkal dan tidak menyalin metode kelas.

Jika Anda menginginkan salinan yang mendalam atau kontrol yang lebih besar atas salinan tersebut maka ada fungsi klon lodash .

Tom
sumber
2
Sejak Object.createmembuat objek baru dengan prototipe tertentu, mengapa tidak lalu saja const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Metode kelas juga akan disalin.
barbatus
1
@barbatus Itu menggunakan prototipe yang salah Blah.prototype != instanceOfBlah. Anda harus menggunakanObject.getPrototypeOf(instanceOfBlah)
Bergi
1
@Bergi tidak, instance kelas ES6 tidak selalu memiliki prototipe. Lihat codepen.io/techniq/pen/qdZeZm yang juga berfungsi dengan instance.
barbatus
@barbatus Maaf, apa? Saya tidak mengikuti. Semua instance memiliki prototipe, itulah yang menjadikannya sebagai instance. Coba kode dari jawaban flori.
Bergi
1
@Bergi Saya pikir itu tergantung pada konfigurasi Babel atau sesuatu. Saya sekarang mengimplementasikan aplikasi asli reaktif dan instance tanpa properti yang diwariskan memiliki prototipe null di sana. Juga seperti yang Anda lihat di sini developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ada kemungkinan getPrototypeOf mengembalikan null.
barbatus
3

Tidak disarankan untuk membuat ekstensi Prototipe, Ini akan mengakibatkan masalah saat Anda akan melakukan tes pada kode / komponen Anda. Kerangka pengujian unit tidak akan secara otomatis mengasumsikan ekstensi prototipe Anda. Jadi ini bukanlah praktik yang baik. Ada lebih banyak penjelasan tentang ekstensi prototipe di sini. Mengapa memperluas objek asli merupakan praktik yang buruk?

Untuk mengkloning objek di JavaScript tidak ada cara yang sederhana atau mudah. Berikut ini contoh pertama yang menggunakan "Shallow Copy":

1 -> Klon dangkal:

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

Hasil:

original.logFullName ():

hasil: Cassio Seffrin

clone.logFullName ():

hasil: John Seffrin

original.address.street;

hasil: 'Street B, 99' // perhatikan bahwa sub objek asli telah diubah

Perhatian: Jika instance memiliki closure sebagai propertinya sendiri, metode ini tidak akan membungkusnya. ( baca lebih lanjut tentang closures ) Dan plus, sub object "address" tidak akan di kloning.

clone.logFullName ()

tidak akan bekerja.

cloneWithPrototype.logFullName ()

akan berfungsi, karena klon juga akan menyalin Prototipe-nya.

Untuk mengkloning array dengan Object.assign:

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

Klon array menggunakan sintaks penyebaran ECMAScript:

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

2 -> Klon Dalam:

Untuk mengarsipkan referensi objek yang benar-benar baru, kita dapat menggunakan JSON.stringify () untuk mengurai objek asli sebagai string dan setelah mengurai kembali ke JSON.parse ().

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

Dengan deep clone, referensi ke alamat akan disimpan. Namun Prototipe deepClone akan dihilangkan, oleh karena itu deepClone.logFullName () tidak akan berfungsi.

3 -> perpustakaan pihak ke-3:

Pilihan lain akan menggunakan pustaka pihak ke-3 seperti loadash atau garis bawah. Mereka akan membuat objek baru dan menyalin setiap nilai dari aslinya ke objek baru dengan menyimpan referensinya di memori.

Garis bawah: biarkan cloneUnderscore = _ (asli) .clone ();

Loadash clone: ​​var cloneLodash = _.cloneDeep (asli);

Kelemahan dari lodash atau garis bawah adalah kebutuhan untuk memasukkan beberapa perpustakaan tambahan dalam proyek Anda. Bagaimanapun mereka adalah pilihan yang baik dan juga menghasilkan hasil kinerja tinggi.

Cassio Seffrin
sumber
1
Saat menetapkan ke {}, klon tidak akan mewarisi metode prototipe apa pun dari aslinya. clone.logFullName()tidak akan bekerja sama sekali. Yang Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)Anda miliki sebelumnya baik-baik saja, mengapa Anda mengubahnya?
Bergi
1
@Bergi thks atas kontribusi Anda, saya sedang mengedit jawaban saya sekarang, saya menambahkan poin Anda untuk menyalin prototipe!
Cassio Seffrin
1
Saya menghargai bantuan Anda @Bergi, Tolong sampaikan pendapat Anda sekarang. Saya sudah menyelesaikan edisi. Saya pikir sekarang jawabannya telah mencakup hampir semua pertanyaan. Thks!
Cassio Seffrin
1
Ya, dan seperti halnya Object.assign({},original), itu tidak berhasil.
Bergi
1
terkadang pendekatan yang lebih sederhana adalah yang kita butuhkan. Jika Anda tidak memerlukan Prototipe dan objek kompleks mungkin hanya "clone = {... original}" dapat menyelesaikan masalah
Cassio Seffrin
0

Satu kapal lagi:

Sebagian besar waktu ... (berfungsi untuk Date, RegExp, Map, String, Number, Array), btw, cloning string, number agak lucu.

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

untuk kelas tersebut tanpa konstruktor salinan:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)
Eric
sumber
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;
}, {});

masukkan deskripsi gambar di sini

Alex Ivasyuv
sumber
-4

Anda dapat menggunakan operator penyebaran, misalnya jika Anda ingin mengkloning sebuah objek bernama Obj:

let clone = { ...obj};

Dan jika Anda ingin mengubah atau menambahkan apa pun ke objek kloning:

let clone = { ...obj, change: "something" };
ttfreeman
sumber