Di section Values dan Types, menyalin nilai terasa tidak bermasalah: let a = 5; let b = a; menghasilkan dua variabel yang berdiri sendiri. Object berperilaku berbeda — dan perbedaan inilah sumber salah satu bug paling sering ditemui pemula: dua variabel yang seharusnya terpisah ternyata menunjuk data yang sama, sehingga mengubah satu membuat yang lain ikut berubah.
Chapter ini membuktikan perilaku referensi itu lewat kode, lalu membahas dua teknik salinan yang benar: spread untuk shallow copy dan structuredClone untuk deep copy. Bagian akhirnya menjawab pertanyaan pendampingnya: karena === tidak membandingkan isi object, bagaimana cara membandingkan dua object dengan benar?
Referensi, Bukan Salinan #
Aturannya: nilai primitive (number, string, boolean, dan sebagainya) disalin by value — isi datanya yang diperbanyak. Object disalin by reference — yang diperbanyak hanyalah alamatnya, bukan isinya. Assignment const b = a pada object tidak membuat object baru; ia membuat nama kedua yang menunjuk object yang sama di memori.
Keputusan desain ini punya alasannya. Object bisa berukuran besar dan bertingkat dalam; menyalin seluruh isinya setiap kali di-assign akan memboros waktu dan memori, padahal sering kali yang kita inginkan justru membagikan data yang sama ke banyak tempat. Karena itu engine memilih membagikan alamat, dan JavaScript menyediakan alat eksplisit — spread, structuredClone — untuk saat salinan yang benar-benar terpisah memang diinginkan. Pemilihan alat yang tepat itulah inti chapter ini.
Akibatnya ada dua keadaan yang berbeda dan mudah tertukar: “dua variabel menunjuk satu object” dan “dua object berisi data yang sama persis”. Operator === pada object menjawab pertanyaan pertama saja — ia membandingkan alamat, bukan isi. Mari kita buktikan semuanya lewat kode.
Membuktikan Referensi dan Membandingkan Manual #
// uji-referensi.js
// Object menyimpan referensi, bukan salinan nilai.
const keranjangA = { total: 150000, item: 3 };
const keranjangB = keranjangA; // referensi yang sama
console.log(keranjangA === keranjangB); // true: satu object, dua nama
keranjangB.total = 175000;
console.log("A total:", keranjangA.total); // ikut berubah
console.log("B total:", keranjangB.total);
// object terpisah, walaupun isinya sama persis
const keranjangC = { total: 175000, item: 3 };
console.log(keranjangA === keranjangC); // false
// perbandingan manual: kunci dan nilai dibandingkan satu per satu
function samaKonten(a, b) {
const kunciA = Object.keys(a);
const kunciB = Object.keys(b);
if (kunciA.length !== kunciB.length) {
return false;
}
for (let i = 0; i < kunciA.length; i++) {
if (a[kunciA[i]] !== b[kunciA[i]]) {
return false;
}
}
return true;
}
console.log(samaKonten(keranjangA, keranjangC)); // true: kontennya identik
console.log(samaKonten(keranjangA, keranjangB)); // true: memang referensi sama
Output yang Dihasilkan:
true
A total: 175000
B total: 175000
false
true
trueMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 5–6 (
const keranjangA = {...}danconst keranjangB = keranjangA;): tidak ada object kedua yang dibuat.keranjangBhanya menambah nama untuk object yang sama dengankeranjangA. - Baris 8 (
console.log(keranjangA === keranjangB);):truekarena keduanya alamat yang sama. Untuk object,===menguji identitas referensi. - Baris 10–12 (
keranjangB.total = 175000;dan seterusnya): perubahan dilakukan lewatkeranjangB, tetapi pembacaan lewatkeranjangAjuga menghasilkan175000— bukti bahwa hanya ada satu object di balik dua nama. Inilah akibat yang paling sering mengejutkan. - Baris 15–16 (
const keranjangC = {...}danconsole.log(...)): object ketiga ditulis dengan isi yang sama persis.===tetap menghasilkanfalse— dua object literal selalu membuat object terpisah. - Baris 19–30 (
function samaKonten(a, b) { ... }): perbandingan manual. Jumlah kunci dibandingkan lebih dulu (object dengan jumlah properti berbeda pasti berbeda), lalu loop membaca setiap kunci dariadan membandingkan nilainya denganb. Ada satu kunci saja yang tidak sama, function langsung mengembalikanfalse. - Baris 32–33 (
console.log(samaKonten(...))):keranjangAdankeranjangCdinilai sama meskipun===menyatakan berbeda — keduanya memang berisi data yang identik. Pemanggilan kedua bernilaitruekarena object yang berreferensi sama tentu berisi sama.
Shallow Copy dengan Spread #
Spread dari chapter Spread Syntax menyalin properti tingkat pertama. Untuk properti yang isinya object lain, yang disalin tetap referensinya — itulah sebabnya salinan jenis ini disebut shallow copy:
// uji-kloning.js
// Spread menyalin tingkat pertama saja; structuredClone menyalin hingga dalam.
const profil = {
nama: "Budi",
kontak: {
email: "[email protected]"
}
};
// spread: shallow copy — nested masih ditunjuk bersama
const salinanSpread = { ...profil };
salinanSpread.nama = "Budi S."; // aman: properti atas terpisah
salinanSpread.kontak.email = "[email protected]"; // nested ikut terdampak
console.log("Spread - nama profil :", profil.nama);
console.log("Spread - email profil:", profil.kontak.email);
// structuredClone: deep copy — nested benar-benar terpisah
const klon = structuredClone(profil);
klon.kontak.email = "[email protected]";
console.log("Clone - email profil :", profil.kontak.email);
console.log("Clone - email klon :", klon.kontak.email);
// structuredClone menangani Date dengan benar
const dokumen = {
judul: "Laporan Kuartal 1",
dibuat: new Date(2026, 0, 15)
};
const klonDokumen = structuredClone(dokumen);
klonDokumen.dibuat.setFullYear(2030);
console.log("Tanggal asli :", dokumen.dibuat.getFullYear());
console.log("Tanggal klon :", klonDokumen.dibuat.getFullYear());Output yang Dihasilkan:
Spread - nama profil : Budi
Spread - email profil: [email protected]
Clone - email profil : [email protected]
Clone - email klon : [email protected]
Tanggal asli : 2026
Tanggal klon : 2030Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 13–15 (
const salinanSpread = { ...profil };dan dua baris berikutnya): spread membuat object baru, sehingga menggantinamapada salinan aman dariprofil. - Baris 17–18 (
console.log("Spread - nama profil :", ...)dan seterusnya): bukti sifat shallow —namaasli bertahan ("Budi"), tetapiprofil.kontak.emailikut menjadi"[email protected]". Propertikontaktidak disalin ulang; asal dan salinan menunjuk object kontak yang sama. - Baris 21–24 (
const klon = structuredClone(profil);dan seterusnya):structuredClonemenyalin seluruh kedalaman struktur. Mengubahklon.kontak.emailkini tidak memengaruhiprofil— kedua email terpisah seutuhnya. - Baris 27–33 (
const dokumen = {...}sampaiconsole.log(...)):Dateadalah object, sehingga shallow copy akan menunjuk tanggal yang sama.structuredClonemenggandakanDate-nya juga: mengubah tahun pada klon menjadi2030tidak menggeser dokumen asli yang tetap2026.
structuredClone juga menangani struktur lain yang berisi referensi, seperti Map dan Set (section Collections) serta referensi melingkar — object yang di dalamnya memuat dirinya sendiri. Untuk data biasa berisi primitive dan object polos, spread dan structuredClone menghasilkan data yang sama; perbedaannya hanya terasa pada tingkat kedalaman.
Alternatif ketiga untuk shallow copy adalah Object.assign({}, sumber) dari chapter Object Methods. Sifatnya identik dengan spread — dan batasannya pun identik:
const asal = { tipe: "meja", laci: { jumlah: 3 } };
const salinan = Object.assign({}, asal);
salinan.laci.jumlah = 5;
console.log(asal.laci.jumlah);Output yang Dihasilkan:
5Properti tipe pada asal aman dari perubahan salinan, tetapi laci tidak: object laci itu tetap ditunjuk bersama, sehingga angka 5 juga terbaca dari asal. Tiga teknik penyalinan — spread, Object.assign, dan manual — sama-sama berhenti di tingkat pertama. Hanya structuredClone yang turun sampai ke dasar.
Batas Perbandingan Manual #
Function samaKonten di atas membandingkan nilai dengan !==. Untuk properti yang isinya object lain, !== kembali membandingkan referensi — sehingga dua object identik tetapi nested bisa dinilai berbeda:
const a = { info: { umur: 20 } };
const b = { info: { umur: 20 } };
console.log(a.info === b.info);Output yang Dihasilkan:
falsePerbandingan isi yang benar-benar menyeluruh harus turun ke setiap tingkat — pekerjaan yang di kode nyata biasanya diserahkan ke pustaka perbandingan atau diperiksa field demi field yang memang relevan. Pilihan terakhir itulah yang paling sering benar: alih-alih menanya “apakah dua object ini identik”, kebutuhan nyata biasanya lebih spesifik — “apakah dua pesanan punya item yang sama”, “apakah profil sudah disunting” — dan masing-masing pertanyaan itu punya definisi sendiri tentang field mana yang dibandingkan. Function perbandingan buatan sendiri, seperti samaKonten, cukup ditulis untuk pertanyaan yang benar-benar ada di program kita.
Kunci dari seluruh chapter ini sederhana: tanyakan selalu apakah yang sedang dibandingkan atau disalin adalah isi atau alamat.
Chapter penutup section ini merangkum kebiasaan baik lainnya: menata object agar tetap ringkas, konsisten, dan mudah dipelihara.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
uji-kloning.js
Unduh berkas// uji-kloning.js
// Spread menyalin tingkat pertama saja; structuredClone menyalin hingga dalam.
const profil = {
nama: "Budi",
kontak: {
email: "[email protected]"
}
};
// spread: shallow copy — nested masih ditunjuk bersama
const salinanSpread = { ...profil };
salinanSpread.nama = "Budi S."; // aman: properti atas terpisah
salinanSpread.kontak.email = "[email protected]"; // nested ikut terdampak
console.log("Spread - nama profil :", profil.nama);
console.log("Spread - email profil:", profil.kontak.email);
// structuredClone: deep copy — nested benar-benar terpisah
const klon = structuredClone(profil);
klon.kontak.email = "[email protected]";
console.log("Clone - email profil :", profil.kontak.email);
console.log("Clone - email klon :", klon.kontak.email);
// structuredClone menangani Date dengan benar
const dokumen = {
judul: "Laporan Kuartal 1",
dibuat: new Date(2026, 0, 15)
};
const klonDokumen = structuredClone(dokumen);
klonDokumen.dibuat.setFullYear(2030);
console.log("Tanggal asli :", dokumen.dibuat.getFullYear());
console.log("Tanggal klon :", klonDokumen.dibuat.getFullYear());
uji-referensi.js
Unduh berkas// uji-referensi.js
// Object menyimpan referensi, bukan salinan nilai.
const keranjangA = { total: 150000, item: 3 };
const keranjangB = keranjangA; // referensi yang sama
console.log(keranjangA === keranjangB); // true: satu object, dua nama
keranjangB.total = 175000;
console.log("A total:", keranjangA.total); // ikut berubah
console.log("B total:", keranjangB.total);
// object terpisah, walaupun isinya sama persis
const keranjangC = { total: 175000, item: 3 };
console.log(keranjangA === keranjangC); // false
// perbandingan manual: kunci dan nilai dibandingkan satu per satu
function samaKonten(a, b) {
const kunciA = Object.keys(a);
const kunciB = Object.keys(b);
if (kunciA.length !== kunciB.length) {
return false;
}
for (let i = 0; i < kunciA.length; i++) {
if (a[kunciA[i]] !== b[kunciA[i]]) {
return false;
}
}
return true;
}
console.log(samaKonten(keranjangA, keranjangC)); // true: kontennya identik
console.log(samaKonten(keranjangA, keranjangB)); // true: memang referensi sama