Anda menelusuri bug melalui tiga file hanya untuk menemukan bahwa sebuah fungsi pembantu yang tidak berbahaya telah mengubah nama seorang pengguna. Anda tidak pernah menyentuh data aslinya. Atau setidaknya begitulah pikir Anda. Dalam JavaScript, menetapkan satu variabel ke variabel lain tidak selalu berarti seperti yang Anda harapkan. Bahasa ini membagi datanya ke dalam dua strategi penyimpanan, dan melupakan strategi mana yang Anda gunakan adalah cara mutasi diam-diam menyelinap ke dalam kode produksi.

Untuk mencegah hal ini, Anda perlu melihat perbedaan antara nilai primitif dan objek persis seperti yang dilakukan oleh engine.

Primitif: Salinan Sebenarnya

Primitif adalah satu datum tunggal yang tidak dapat dibagi. Ia tidak dapat diuraikan menjadi nilai JavaScript yang lebih kecil. Bahasa ini mendefinisikan tujuh tipe primitif: String, Number, Boolean, Undefined, Null, Symbol, dan BigInt.

Karena primitif bersifat atomik, mereka umumnya berada langsung di dalam pengikatan (binding) variabel. Saat Anda menyalin satu variabel primitif ke variabel lain, engine menduplikasi data yang sebenarnya. Setiap variabel menerima slot independennya sendiri di dalam memori.

let a = "Alina";
let b = a;
b = "Ali";

console.log(a); // "Alina"
console.log(b); // "Ali"

Di sini, a tetap tidak tersentuh. Menetapkan ulang b membuat nilai yang benar-benar baru dan mengarahkan b ke nilai tersebut, sementara a masih memegang string aslinya. Perilaku ini adalah copy by value. Hal ini bekerja secara identik untuk angka, boolean, symbol, dan seluruh keluarga primitif lainnya. Anda dapat memasukkannya ke dalam fungsi, menetapkannya kembali, atau mengembalikannya tanpa perlu khawatir tentang efek samping pada variabel saudara (sibling variables).

Objek: Alamat yang Dibagi

Objek berbeda. Objek adalah wadah komposit yang mengelompokkan beberapa potongan data menjadi satu. Kategori ini mencakup objek biasa (plain objects), array, fungsi, tanggal, dan semua tipe non-primitif lainnya. Karena struktur ini dapat tumbuh besar dan bersarang (nested), JavaScript tidak menyimpan seluruh objek di dalam variabel. Sebaliknya, variabel tersebut menyimpan sebuah referensi, yang pada dasarnya adalah alamat memori yang menunjuk ke data asli yang disimpan di tempat lain.

Saat Anda menetapkan sebuah objek ke variabel baru, engine menyalin alamatnya, bukan objeknya. Dua variabel sekarang menunjuk ke rumah yang sama persis.

const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";

console.log(user.name); // "Ali"

Mengubah copy.name juga mengubah user.name karena kedua nama tersebut merujuk ke objek dasar yang sama. Ini adalah copy by reference. Kejutan yang sama muncul pada array:

const scores = [82, 91, 74];
const backup = scores;
backup.push(88);

console.log(scores); // [82, 91, 74, 88]

Hanya ada satu array di dalam memori. scores dan backup hanyalah dua papan penunjuk yang mengarah ke array tersebut.

Kata kunci const menambah lapisan kebingungan. Mendeklarasikan objek dengan const mengunci pengikatan variabel sehingga Anda tidak dapat mengarahkannya ke alamat baru. Ini tidak mengunci objek itu sendiri.

const settings = { theme: "dark" };
settings.theme = "light";        // Works perfectly.
settings = { theme: "dark" };    // TypeError

Pengembang sering kali mengharapkan const menjanjikan imutabilitas (immutability). Padahal tidak. const hanya mencegah penetapan ulang (reassignment) pada referensi tersebut. Jika Anda ingin isinya tersegel, Anda harus menyalinnya dengan sengaja.

Di Mana Bug yang Sebenarnya Berada

Bug referensi jarang muncul sebagai penetapan ulang variabel yang jelas. Mereka bersembunyi di dalam pemanggilan fungsi.

function addTimestamp(record) {
  record.timestamp = Date.now();
  return record;
}

const original = { id: 1 };
addTimestamp(original);

console.log(original.timestamp); // A number now exists here. Oops.

Parameter record menerima salinan referensi. Mutasi properti apa pun di dalam fungsi tersebut langsung menulis ke objek pemanggil (caller). Fungsi tersebut tampak seperti transformasi sederhana, namun ia membocorkan status (state) melintasi batas cakupan (scope boundaries).

Pola ini sangat menyakitkan dalam framework UI seperti React, di mana pembaruan status bergantung