Membuat salinan dalam dalam JavaScript menggunakan structuredClone

Platform kini dilengkapi dengan structuredClone(), fungsi bawaan untuk deep-copy.

Selama ini, Anda harus menggunakan solusi dan library untuk membuat deep copy nilai JavaScript. Platform kini dilengkapi dengan structuredClone(), fungsi bawaan untuk deep-copying.

Dukungan Browser

  • Chrome: 98.
  • Edge: 98.
  • Firefox: 94.
  • Safari: 15.4.

Sumber

Salinan dangkal

Menyalin nilai di JavaScript hampir selalu dangkal, bukan dalam. Artinya, perubahan pada nilai bertingkat yang dalam akan terlihat di salinan serta di dokumen asli.

Salah satu cara untuk membuat salinan dangkal di JavaScript menggunakan operator penyebaran objek ...:

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

Menambahkan atau mengubah properti secara langsung di salinan dangkal hanya akan memengaruhi salinan, bukan salinan asli:

myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`

Namun, menambahkan atau mengubah properti bertingkat dalam akan memengaruhi baik salinan maupun yang asli:

myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp) 
// ^ logs `a new value`

Ekspresi {...myOriginal} melakukan iterasi pada properti (yang dapat dihitung) myOriginal menggunakan Operator Spread. Fungsi ini menggunakan nama dan nilai properti, lalu menetapkannya satu per satu ke objek kosong yang baru dibuat. Dengan demikian, objek yang dihasilkan memiliki bentuk yang identik, tetapi dengan salinan daftar properti dan nilainya sendiri. Nilai juga disalin, tetapi nilai primitif yang disebut ditangani secara berbeda oleh nilai JavaScript daripada nilai non-primitif. Untuk mengutip MDN:

Di JavaScript, primitif (nilai primitif, jenis data primitif) adalah data yang bukan objek dan tidak memiliki metode. Ada tujuh jenis data primitif: string, angka, bigint, boolean, undefined, simbol, dan null.

MDN — Primitive

Nilai non-primitif ditangani sebagai referensi, yang berarti bahwa tindakan menyalin nilai sebenarnya hanya menyalin referensi ke objek pokok yang sama, sehingga menghasilkan perilaku salinan dangkal.

Salinan mendalam

Kebalikan dari salinan dangkal adalah salinan mendalam. Algoritma deep copy juga menyalin properti objek satu per satu, tetapi memanggil dirinya sendiri secara rekursif saat menemukan referensi ke objek lain, sehingga juga membuat salinan objek tersebut. Hal ini dapat sangat penting untuk memastikan bahwa dua bagian kode tidak secara tidak sengaja berbagi objek dan tanpa sadar memanipulasi status satu sama lain.

Dulu tidak ada cara yang mudah atau bagus untuk membuat deep-copy nilai di JavaScript. Banyak orang mengandalkan library pihak ketiga seperti fungsi cloneDeep() Lodash. Solusi yang paling umum untuk masalah ini adalah hack berbasis JSON:

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));

Bahkan, ini adalah solusi yang sangat populer, sehingga V8 secara agresif mengoptimalkan JSON.parse() dan khususnya pola di atas untuk membuatnya secepat mungkin. Meskipun cepat, metode ini memiliki beberapa kekurangan dan masalah:

  • Struktur data rekursif: JSON.stringify() akan ditampilkan saat Anda memberinya struktur data rekursif. Hal ini dapat terjadi dengan mudah saat menggunakan linked list atau hierarki.
  • Jenis bawaan: JSON.stringify() akan ditampilkan jika nilai berisi bawaan JS lainnya seperti Map, Set, Date, RegExp, atau ArrayBuffer.
  • Fungsi: JSON.stringify() akan menghapus fungsi secara diam-diam.

Cloning terstruktur

Platform ini sudah memerlukan kemampuan untuk membuat salinan mendalam nilai JavaScript di beberapa tempat: Menyimpan nilai JS di IndexedDB memerlukan beberapa bentuk serialisasi sehingga dapat disimpan di disk dan kemudian diserialisasi ulang untuk memulihkan nilai JS. Demikian pula, mengirim pesan ke WebWorker melalui postMessage() memerlukan transfer nilai JS dari satu wilayah JS ke wilayah JS lainnya. Algoritme yang digunakan untuk ini disebut “Clone Terstruktur”, dan hingga baru-baru ini, tidak mudah diakses oleh developer.

Sekarang, hal tersebut telah berubah. Spesifikasi HTML diubah untuk mengekspos fungsi yang disebut structuredClone() yang menjalankan algoritma tersebut secara persis sebagai cara bagi developer untuk membuat salinan mendalam nilai JavaScript dengan mudah.

const myDeepCopy = structuredClone(myOriginal);

Selesai! Itu adalah seluruh API. Jika Anda ingin mempelajari detailnya lebih lanjut, lihat artikel MDN.

Fitur dan batasan

Cloning terstruktur mengatasi banyak (meskipun tidak semua) kekurangan teknik JSON.stringify(). Cloning terstruktur dapat menangani struktur data siklus, mendukung banyak jenis data bawaan, dan umumnya lebih andal dan sering kali lebih cepat.

Namun, fitur ini masih memiliki beberapa batasan yang mungkin tidak Anda duga:

  • Prototipe: Jika menggunakan structuredClone() dengan instance class, Anda akan mendapatkan objek biasa sebagai nilai yang ditampilkan, karena cloning terstruktur menghapus rantai prototipe objek.
  • Fungsi: Jika objek Anda berisi fungsi, structuredClone() akan menampilkan pengecualian DataCloneError.
  • Tidak dapat di-clone: Beberapa nilai tidak terstruktur dan dapat di-clone, terutama node Error dan DOM. Hal ini akan menyebabkan structuredClone() ditampilkan.

Jika salah satu batasan ini menjadi masalah bagi kasus penggunaan Anda, library seperti Lodash masih menyediakan implementasi kustom algoritma deep cloning lainnya yang mungkin atau mungkin tidak sesuai dengan kasus penggunaan Anda.

Performa

Meskipun belum melakukan perbandingan micro-benchmark baru, saya melakukan perbandingan pada awal tahun 2018, sebelum structuredClone() diekspos. Saat itu, JSON.parse() adalah opsi tercepat untuk objek yang sangat kecil. Saya berharap hal itu tetap sama. Teknik yang mengandalkan cloning terstruktur (secara signifikan) lebih cepat untuk objek yang lebih besar. Mengingat structuredClone() baru tidak memiliki overhead untuk menyalahgunakan API lain dan lebih andal daripada JSON.parse(), sebaiknya Anda menjadikannya pendekatan default untuk membuat deep copy.

Kesimpulan

Jika Anda perlu membuat deep-copy nilai di JS—mungkin karena Anda menggunakan struktur data yang tidak dapat diubah atau ingin memastikan fungsi dapat memanipulasi objek tanpa memengaruhi yang asli—Anda tidak perlu lagi mencari solusi atau library. Ekosistem JS kini memiliki structuredClone(). Hore.