JavaScript dan TypeScript menjadikannya sangat mudah (dan berbahaya) untuk menganggap rujukan objek sebagai sesuatu yang boleh dibuang. Anda mencipta satu objek, menghantarnya ke satu fungsi, menyimpannya dalam cache, dan kemudian menggantikan pemboleh ubah tersebut dengan instans baharu. Bahasa pengaturcaraan tidak akan membantah. Rujukan lama masih wujud di tempat lain dalam program anda, merujuk kepada data yang tidak lagi terkini. Ini bukan satu kegagalan sistem (crash). Ia adalah sesuatu yang lebih buruk: perbezaan senyap antara dua bahagian kod anda yang kedua-duanya menyangka mereka memiliki kebenaran yang sahih.

Disiplin yang mencegah perkara ini dipanggil Hard Object References. Ia bukan sebuah perpustakaan (library) atau ciri pengkompil (compiler feature). Ia adalah satu kontrak yang anda laksanakan merentasi kod anda.

Masalah Alias Lapuk (Stale Alias)

Alias lapuk berlaku apabila satu modul memegang rujukan kepada satu objek sementara modul lain menggantikan objek tersebut dengan objek baharu. Rujukan pertama masih merupakan kod yang sah, tetapi ia tidak lagi merujuk kepada data semasa.

Bayangkan rekod pengguna dalam aplikasi web tipikal:

const user = {
  name: "Alice",
  address: {
    city: "Seoul",
    country: "KR"
  }
};

Satu komponen penghantaran menangkap alamat tersebut pada peringkat awal:

const shippingAddress = user.address;

Kemudian, kemas kini profil tiba. Satu reducer atau pengendali servis memutuskan untuk menggantikan keseluruhan objek tersebut:

user.address = { city: "Tokyo", country: "JP" };

Pada tahap ini, user.address merujuk kepada Tokyo. Tetapi shippingAddress masih merujuk kepada objek lama di Seoul. Tiada pengecualian (exception) dicetuskan. TypeScript tidak mengalami masalah kerana jenis (types) masih sepadan. UI mungkin memaparkan bandar yang telah dikemas kini pada halaman profil, manakala label penghantaran secara senyap mencetak bandar yang lama. Pepijat (bug) ini hanya muncul apabila pengguna mengadu bahawa bungkusan mereka dihantar ke negara yang salah.

Ini berlaku kerana JavaScript memisahkan identiti daripada nilai. Apabila anda menggantikan sifat (property) objek dengan literal objek yang baharu, anda memutuskan rantaian tersebut. Objek lama tidak dimusnahkan; ia sekadar menjadi yatim (orphaned). Sesiapa yang masih memegangnya sedang bekerja dengan "hantu".

Maksud Hard Object References

Peraturannya mudah: gantikan nilai primitif, tetapi jangan sekali-kali menggantikan rujukan objek atau tatasusunan (array). Apabila data baharu tiba, anda menyalinnya ke dalam bekas (container) sedia ada dan bukannya menukar bekas tersebut.

Ini memerlukan tiga tabiat konkrit.

Pertama, isytiharkan objek dan tatasusunan dengan const. Ini menghapuskan godaan untuk mengikat semula (rebind) pemboleh ubah peringkat teratas kepada instans baharu. Pemboleh ubah tersebut harus kekal tetap sepanjang hayat skop tersebut.

Kedua, jangan sesekali menggantikan sifat yang memegang objek atau tatasusunan dengan yang baharu dicipta. Jika anda perlu mengemas kini alamat, ubah suai (mutate) sifat-sifat di dalamnya.

Ketiga, jika anda perlu mengosongkan atau menetapkan semula (reset) keadaan (state), kosongkan struktur sedia ada daripada membuangnya untuk objek atau tatasusunan kosong yang baharu.

Meninjau semula contoh alamat tadi, kemas kini yang betul adalah seperti ini:

user.address.city = "Tokyo";
user.address.country = "JP";

Jika data yang masuk adalah separa atau dinamik, gunakan Object.assign untuk menulis ke dalam sasaran sedia ada:

Object.assign(user.address, incomingAddressData);

Pemboleh ubah shippingAddress, yang merujuk kepada objek yang sama tepat dalam memori, kini dapat melihat medan baharu tersebut dengan serta-merta. Hanya terdapat satu objek kanonikal yang berfungsi sebagai sumber kebenaran (source of truth) yang aktif.

Di Mana Ini Paling Penting

Disiplin ini mungkin kelihatan berlebihan untuk objek konfigurasi yang rata (flat). Ia menjadi penting sebaik sahaja keadaan (state) anda berkembang menjadi sebuah graf di mana pelbagai subsistem memegang penunjuk (pointers) ke nod yang bertindih.

Pertimbangkan sebuah penyunting teks kaya (rich text editor). Model dokumen adalah sebuah pokok nod. Model pemilihan memegang rujukan ke nod mula dan akhir. Penimbal sejarah (history buffer) memegang rujukan ke nod yang berubah dalam operasi terakhir. Lapisan rendering memegang rujukan ke nod yang diukurnya untuk susun atur (layout). Jika pengurus keadaan menggantikan nod perenggan dengan objek baharu kerana teksnya berubah, setiap satu daripada subsistem tersebut kini memegang alias lapuk. Pemilihan akan menonjolkan kawasan yang salah. Sistem sejarah tidak dapat berpatah balik (revert) dengan betul. Renderer mengalami kegagalan atau, lebih teruk lagi, memaparkan kursor hantu.

Risiko yang sama muncul dalam profil pengguna dengan tetapan bersarang (nested settings) dan kebenaran yang dirujuk oleh UI, lapisan kawalan akses, dan rutin simpanan automatik. Ia muncul dalam enjin susun atur di mana bekas induk menyimpan cache ukuran nod anak. Ia muncul dalam penyunting visual dan alatan kanvas di mana pengawal masa larian (runtime controller) menjejaki entiti aktif melalui rujukan. Dalam semua domain ini, komponen mengambil pemegang (handle) kepada satu objek dan mengharapkan pemegang tersebut kekal sebagai paparan langsung bagi kebenaran.

Hard Object References melayan objek sebagai alamat yang stabil. Perabot di dalamnya boleh berubah, tetapi pintu kekal di tempat yang sama. Sesiapa yang memegang alamat tersebut boleh masuk dan melihat susun atur semasa.

Reaktiviti dan bukannya Penggantian

If you have worked with Redux or similar immutable state libraries, this model probably sounds backward. In those systems, change is signaled by producing a new object. The reference change is the signal. Components compare prevProps.data === nextProps.data to know whether to re-render.

Hard Object References require you to flip that assumption. Because the reference stays constant, reference equality tells you nothing about whether the data changed. You need a different way to broadcast updates.

In practice, this means relying on reactivity systems, explicit observers, or dirty flags. Mutating user.address.city can trigger a setter that notifies subscribers. An object can emit a change event through an event bus. A game loop or canvas tool might set a global dirty flag and re-scan the graph at the end of the frame. The reference is stable, so you must make the data flow visible through other mechanisms.

This architectural shift is why the approach fits best in complex frontend state, large component-local state, visual editors, canvas tools, and runtime controllers. These systems already rely on granular updates, direct mutations, or imperative APIs. Forcing immutability on top often creates excessive allocation pressure and reference churn without buying proportional clarity. When every frame matters, allocating a new object graph just to move a slider is wasteful. Keeping the reference hard and mutating the interior matches the actual mechanics of the problem.

Making It Stick

One of the overlooked benefits of this rule is