Anda menjejaki pepijat melalui tiga fail hanya untuk mendapati bahawa satu fungsi pembantu yang tidak berbahaya telah menamakan semula seorang pengguna. Anda tidak pernah menyentuh data asal. Atau sekurang-kurangnya itulah yang anda sangka. Dalam JavaScript, menetapkan satu pemboleh ubah kepada pemboleh ubah yang lain tidak sentiasa bermaksud seperti yang anda jangkakan. Bahasa ini membahagikan datanya kepada dua strategi penyimpanan, dan kegagalan untuk mengingati strategi mana yang anda gunakan adalah punca mutasi senyap menyusup masuk ke dalam kod produksi.
Untuk mengelakkan perkara ini, anda perlu melihat perbezaan antara nilai primitif dan objek tepat seperti yang dilakukan oleh enjin.
Primitif: Salinan Sebenar
Primitif ialah satu datum tunggal yang tidak boleh dibahagikan. Ia tidak boleh dipecahkan kepada nilai JavaScript yang lebih kecil. Bahasa ini mentakrifkan tujuh jenis primitif: String, Number, Boolean, Undefined, Null, Symbol, dan BigInt.
Oleh kerana primitif bersifat atomik, ia secara amnya berada terus di dalam ikatan pemboleh ubah. Apabila anda menyalin satu pemboleh ubah primitif ke pemboleh ubah yang lain, enjin akan menduplikasi data sebenar tersebut. Setiap pemboleh ubah menerima slot bebasnya sendiri dalam memori.
let a = "Alina";
let b = a;
b = "Ali";
console.log(a); // "Alina"
console.log(b); // "Ali"
Di sini, a kekal tidak berubah. Menetapkan semula b mencipta nilai yang benar-benar baharu dan menghalakan b kepadanya, manakala a masih memegang rentetan asalnya. Tingkah laku ini adalah salinan mengikut nilai (copy by value). Ia berfungsi secara identik untuk nombor, boolean, simbol, dan selebihnya daripada keluarga primitif. Anda boleh menghantarnya ke dalam fungsi, menetapkannya semula, atau mengembalikannya tanpa perlu risau tentang kesan sampingan pada pemboleh ubah berkaitan.
Objek: Alamat Kongsi
Objek adalah berbeza. Objek ialah bekas komposit yang mengumpulkan pelbagai kepingan data bersama-sama. Kategori ini termasuk objek biasa, tatasusunan, fungsi, tarikh, dan setiap jenis bukan primitif yang lain. Oleh kerana struktur ini boleh menjadi besar dan bersarang, JavaScript tidak menyimpan keseluruhan objek di dalam pemboleh ubah. Sebaliknya, pemboleh ubah tersebut memegang rujukan, yang pada dasarnya merupakan alamat memori yang menghala ke data sebenar yang disimpan di tempat lain.
Apabila anda menetapkan objek kepada pemboleh ubah baharu, enjin menyalin alamatnya, bukan objek tersebut. Dua pemboleh ubah kini menghala ke rumah yang sama tepat.
const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";
console.log(user.name); // "Ali"
Menukar copy.name juga menukar user.name kerana kedua-dua nama tersebut merujuk kepada objek asas yang sama. Ini adalah salinan mengikut rujukan (copy by reference). Kejutan yang sama berlaku dengan tatasusunan:
const scores = [82, 91, 74];
const backup = scores;
backup.push(88);
console.log(scores); // [82, 91, 74, 88]
Hanya terdapat satu tatasusunan dalam memori. scores dan backup hanyalah dua papan tanda yang menghala kepadanya.
Kata kunci const menambah satu lapisan kekeliruan. Mengisytiharkan objek dengan const mengunci ikatan pemboleh ubah supaya anda tidak boleh menghalakannya ke alamat baharu. Ia tidak mengunci objek itu sendiri.
const settings = { theme: "dark" };
settings.theme = "light"; // Works perfectly.
settings = { theme: "dark" }; // TypeError
Pembangun sering menjangkakan const menjanjikan ketidakterubahan (immutability). Ia tidak begitu. Ia hanya menghalang penetapan semula rujukan. Jika anda mahu kandungannya dikunci, anda mesti menyalin dengan niat yang jelas.
Di Mana Pepijat Sebenar Berada
Pepijat rujukan jarang muncul sebagai penetapan semula pemboleh ubah yang ketara. Ia bersembunyi di dalam panggilan 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 rujukan tersebut. Sebarang mutasi harta di dalam fungsi itu menulis terus ke dalam objek pemanggil. Fungsi tersebut kelihatan seperti transformasi ringkas, namun ia membocorkan keadaan merentasi sempadan skop.
Corak ini amat menyakitkan dalam rangka kerja UI seperti React, di mana kemas kini keadaan bergantung