Membuat salinan dalam dalam JavaScript menggunakan structuredClone

Platform ini kini dilengkapi dengan structuredClone(), fungsi bawaan untuk penyalinan mendalam.

Untuk waktu yang lama, Anda harus menggunakan solusi dan library untuk membuat salinan mendalam dari nilai JavaScript. Platform ini kini dilengkapi dengan structuredClone(), fungsi bawaan untuk penyalinan mendalam.

Dukungan Browser

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

Sumber

Salinan dangkal

Menyalin nilai dalam JavaScript hampir selalu dangkal, dibandingkan dengan deep. Hal ini berarti perubahan pada nilai bertingkat yang dalam akan terlihat di salinan serta aslinya.

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 pada shallow copy hanya akan memengaruhi salinan, bukan aslinya:

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

Namun, menambahkan atau mengubah properti bertingkat yang dalam akan memengaruhi baik salinan maupun 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) dari myOriginal menggunakan Operator Penyebaran. Fungsi ini menggunakan nama dan nilai properti, dan menetapkannya satu per satu ke objek yang baru dibuat dan kosong. Dengan demikian, objek yang dihasilkan memiliki bentuk yang identik, tetapi dengan salinan daftar properti dan nilainya sendiri. Nilai tersebut juga disalin, tetapi nilai yang disebut nilai primitif ditangani secara berbeda oleh nilai JavaScript dibandingkan dengan nilai non-primitif. Untuk mengutip MDN:

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

MDN — Primitif

Nilai non-primitif ditangani sebagai referensi, artinya tindakan penyalinan nilai sebenarnya hanyalah menyalin referensi ke objek dasar yang sama, sehingga menghasilkan perilaku shallow copy.

Salinan mendalam

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

Dahulu tidak ada cara yang mudah atau menyenangkan untuk membuat salinan mendalam suatu nilai di JavaScript. Banyak orang mengandalkan library pihak ketiga seperti fungsi cloneDeep() Lodash. Bisa dibilang solusi yang paling umum untuk masalah ini adalah peretasan berbasis JSON:

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

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

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

Cloning terstruktur

Platform ini sudah membutuhkan kemampuan untuk membuat salinan mendalam nilai JavaScript di beberapa tempat: Menyimpan nilai JS di IndexedDB memerlukan beberapa bentuk serialisasi sehingga dapat disimpan pada disk dan kemudian di-deserialisasi untuk memulihkan nilai JS. Demikian pula, pengiriman pesan ke WebWorker melalui postMessage() memerlukan transfer nilai JS dari satu realm JS ke realm JS lainnya. Algoritma yang digunakan untuk analisis ini disebut “Clone Terstruktur”, dan hingga saat ini, fitur ini tidak dapat diakses dengan mudah oleh developer.

Hal itu sekarang telah berubah. Spesifikasi HTML telah diubah untuk mengekspos fungsi bernama structuredClone() yang menjalankan persis algoritma tersebut sebagai sarana bagi developer untuk membuat salinan mendalam nilai JavaScript.

const myDeepCopy = structuredClone(myOriginal);

Selesai! Ini adalah keseluruhan API. Jika Anda ingin mempelajari detailnya lebih lanjut, lihat artikel MMD.

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, serta umumnya lebih andal dan sering kali lebih cepat.

Namun, terdapat beberapa batasan yang mungkin membuat Anda lengah:

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

Jika salah satu dari batasan ini menjadi pemecah kesepakatan untuk kasus penggunaan Anda, library seperti Lodash masih menyediakan implementasi kustom dari algoritma cloning mendalam lainnya yang mungkin sesuai atau tidak sesuai dengan kasus penggunaan Anda.

Performa

Meskipun saya belum melakukan perbandingan tolok ukur mikro baru, saya melakukan perbandingan pada awal tahun 2018, sebelum structuredClone() ditampilkan. Saat itu, JSON.parse() adalah opsi tercepat untuk objek yang sangat kecil. Saya harap hal itu akan tetap sama. Teknik yang mengandalkan cloning terstruktur (jauh) lebih cepat untuk objek yang lebih besar. Mengingat structuredClone() baru tidak dikenai overhead penyalahgunaan API lain dan lebih tangguh daripada JSON.parse(), sebaiknya jadikan ini pendekatan default Anda untuk membuat salinan dalam.

Kesimpulan

Jika Anda perlu membuat salinan mendalam suatu nilai di JS—mungkin karena Anda menggunakan struktur data yang tidak dapat diubah atau Anda ingin memastikan suatu fungsi dapat memanipulasi objek tanpa memengaruhi aslinya—Anda tidak perlu lagi mencari solusi atau library. Ekosistem JS sekarang memiliki structuredClone(). Hore.