Copy Object: Shallow vs Deep
Menyalin object dengan benar: kenapa assignment biasa bukan salinan, beda shallow copy dan deep copy, dan kapan memakai spread, Object.assign, atau structuredClone. Salah paham soal referensi adalah sumber bug paling licin di JavaScript.
Masalahnya: Assignment Bukan Salinan
Untuk tipe primitif (number, string, boolean), assignment menyalin NILAI. Untuk object dan array, assignment menyalin REFERENSI, yaitu "alamat" menuju data yang sama:
const a = { nama: "Budi", nilai: [80, 90] };
const b = a; // b menunjuk ke object YANG SAMA, bukan salinan!
b.nama = "Ani";
console.log(a.nama); // "Ani" (ikut berubah!)Analoginya: a dan b seperti dua kunci menuju satu rumah yang sama. Mengecat lewat kunci b tetap mengubah rumah yang dilihat lewat kunci a. Bug dari kesalahpahaman ini licin karena kodenya terlihat benar dan errornya muncul jauh dari penyebabnya: kamu ubah "salinan", tapi data asli di tempat lain ikut rusak.
Shallow Copy: Satu Level Saja
Shallow copy membuat object BARU, tapi hanya menyalin satu level teratas. Properti primitif jadi milik masing-masing, tapi object/array bertingkat di dalamnya masih berbagi referensi.
const c = { ...a }; // atau Object.assign({}, a)
c.nama = "Citra";
console.log(a.nama); // "Budi" (aman, level pertama terpisah)
c.nilai.push(100);
console.log(a.nilai); // [80, 90, 100] (MASIH ikut! array dalam tetap satu)Visualisasinya: rumahnya sudah dua (object luar terpisah), tapi lemari di dalamnya (array nilai) masih satu yang dipakai bersama. Cocok untuk object datar atau saat kamu hanya mengubah properti level pertama, seperti pola update imutabel { ...profil, umur: 18 }.
Contoh nyata yang aman dengan shallow copy:
const pengaturan = { tema: "dark", bahasa: "id" };
const percobaan = { ...pengaturan, tema: "light" };
console.log(pengaturan.tema); // "dark", asli tidak tersentuhDeep Copy: structuredClone
Deep copy menyalin SELURUH kedalaman: tiap level menjadi milik masing-masing, tidak ada lagi yang berbagi.
const d = structuredClone(a);
d.nilai.push(100);
d.nama = "Dewi";
console.log(a.nilai); // [80, 90] (aman total!)
console.log(a.nama); // "Budi" (aman total!)structuredClone tersedia di browser modern dan Node 17+. Ia menangani Date, Map, Set, dan bahkan referensi sirkular dengan benar. Alternatif lama yang masih sering terlihat:
const e = JSON.parse(JSON.stringify(a));Trik JSON ini hanya untuk data polos: function dan undefined hilang, Date jadi string, Infinity/NaN jadi null, dan referensi sirkular bikin error. Kalau ragu, pilih structuredClone.
Kapan Pakai yang Mana?
- Tidak butuh salinan? Pakai referensi biasa, sadar risikonya.
- Object datar + update satu level? Spread
{...obj}. Cepat dan idiomatis. - Data bertingkat yang akan dimutasi?
structuredClone. Sedikit biaya performa demi keamanan. - Object raksasa yang sering disalin? Pikir ulang desainnya; deep copy berulang itu mahal.
Hindari deep copy di loop besar atau render yang sering: salin hanya yang perlu, sedalam yang perlu.
Contoh Nyata: Bug Draf Edit yang Merusak Data Asli
Kasus klasik: halaman profil menampilkan data user, tombol "Edit" membuka form draf yang seharusnya tidak menyentuh data asli sebelum Simpan ditekan.
const draf = { ...userAktif }; // shallow copy!
draf.nama = "Budi Edited"; // aman, level pertama terpisah
draf.alamat.kota = "Jakarta"; // BAHAYA! alamat asli ikut berubah!Halaman profil ikut berubah kotanya padahal user belum menekan Simpan, dan tombol "Batal" tidak bisa mengembalikan keadaan. Tidak ada error sama sekali, datanya hanya "berubah sendiri", sehingga bug ini sulit dilacak. Solusinya: karena alamat bertingkat dan akan dimutasi form, pakai structuredClone(userAktif) sejak awal agar draf dan data asli sepenuhnya terpisah.
Jebakan Umum
Membandingkan object dengan ===. Karena === pada object membandingkan referensi (alamat rumah), bukan isi, dua object dengan isi identik tetap tidak sama:
console.log({ a: 1 } === { a: 1 }); // false!Untuk membandingkan isi, bandingkan propertinya satu per satu atau pakai trik JSON.stringify(x) === JSON.stringify(y) (dengan catatan urutan key harus sama).
Shallow copy array of object. [...arr] menyalin array-nya, tapi tiap object di dalamnya masih referensi yang sama. Mengubah salin[0].nama tetap merusak asli[0].nama. Untuk array of object yang akan dimutasi, pakai structuredClone(arr) atau arr.map(o => ({...o})) untuk salinan satu level per item.
Catatan teknis:
Object.assign(target, ...sumber)menyalin properti enumerable milik sendiri dari sumber ke target dan me-return target. Hati-hati: ia MEMUTASI target, jadi pola amannya selaluObject.assign({}, sumber). Spread object{...obj}pada dasarnya adalah versi sintaks yang lebih ringkas dengan perilaku yang sama.
export default function App(): JSX.Element { return <h1>Hello world</h1> }
Tantangan
Salinan Aman
Diberi config = {theme: "dark", font: {size: 14}}. Buat salinan, ubah font.size jadi 16 di salinan, pastikan config asli TIDAK berubah. Buktikan dengan console.log. Kebutuhan salinan aman seperti ini muncul terus di aplikasi nyata: menduplikasi draf formulir sebelum user mengedit, membuat snapshot state sebelum simulasi, atau menyalin template dokumen yang akan dikustomisasi per user.
<!doctype html> <html> <head> <meta charset="utf-8" /> </head> <body> <h1>Halo JS</h1> <p>Buka console preview untuk melihat output.</p> <script src="index.js"></script> </body> </html>