Belajar dengan membaca secara berurutan

Reference dan Copying — Jebakan Array dan Object

Pelajari kenapa array dan object yang di-assign saling menunjuk, lalu cara menyalinnya dengan benar.

Coba backup sebuah order sebelum mengeditnya dengan menulis const backup = order;, dan memperbaiki order belakangan mengubah backup juga. Ini tidak pernah terjadi pada number atau string.

Artikel ini membahas apa yang sebenarnya diteruskan saat kamu meng-assign array atau object, dan cara membangun copy yang aman.

Apa yang Diteruskan Assignment — Primitive vs Reference

Yang bikin ketidakcocokan ini susah dilacak adalah apa yang diteruskan saat assignment berbeda tergantung jenis value-nya. Di sini kita rangkum apa yang tersalin apa adanya saat assignment, dan apa yang tidak.

5 jenis — number, string, boolean, null, dan undefined (disebut primitive — value yang bukan object maupun array) — mendapat value-nya sendiri yang di-copy saat kamu meng-assign-nya.

Kedua variable-nya tidak berhubungan lagi setelah copy itu, jadi mengubah satu tidak pernah mengubah yang lain.

Array dan object adalah reference type (type yang menyimpan informasi yang menunjuk ke tempat sebuah value hidup, alih-alih value itu sendiri). Assignment cuma meneruskan informasi lokasi itu, jadi 2 variable berujung menunjuk ke array atau object yang persis sama.

Tulis ulang lewat nama mana pun, dan kamu menulis ulang hal yang sama persis, tidak peduli lewat mana.

Apakah Assignment Meneruskan Value atau Lokasi?
Assign numberatau stringValue-nya sendiriyang di-copyDisimpan sebagai2 value terpisahArrayatau objectCuma info lokasiyang diteruskan2 variable menunjukke 1 halTulis ulang lewat1 namaMenunjuk ke 1 halyang sama persisYang lainikut berubah
Primitive dapat value-nya sendiri yang di-copy dan jadi independen, tapi array atau object punya 2 variable yang menunjuk ke 1 hal yang sama.
// Primitive dapat value-nya sendiri yang di-copy
let price = 3980;
let salePrice = price;
salePrice = 2980;
console.log(price);          // 3980 (tidak terpengaruh)
console.log(salePrice);      // 2980

// Array menunjuk ke 1 hal yang sama
const cart = ["A4 Notebook"];
const savedCart = cart;
savedCart.push("Permanent Marker");
console.log(cart.length);    // 2 (cart ikut bertambah)
console.log(cart === savedCart);  // true (keduanya menunjuk ke hal yang sama)

Bahkan dengan const, Isinya Bisa Ditulis Ulang

Yang dikunci const cuma di mana variable-nya menunjuk. Tulis const cart = [];, lalu cart.push("Permanent Marker"), dan isinya bertambah; const order = {}, lalu order.total = 0, juga berhasil.

Yang dilarang cuma reassignment yang menunjuknya ke tempat lain sama sekali, seperti cart = [...].

Tulis ulang apa yang kamu kira backup sebuah order, dan lihat apa yang terjadi. order dan backup sudah dideklarasikan, dengan backup diberi order langsung.

① Ambil total-nya ke variable terpisah (pakai let, karena kamu akan me-reassign-nya), reassign hanya variable itu jadi 500, dan tampilkan variable itu diikuti total order.

② Tulis ulang total backup jadi 0, dan tampilkan total backup.

③ Tampilkan total order aslinya.

④ Tampilkan apakah order dan backup menunjuk ke hal yang sama.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Membangun Copy — Apa yang Sebenarnya Dilihat ===

Seperti yang baru kamu lihat, assignment saja nggak menghasilkan copy yang aman dan terpisah dari aslinya. Mau order terpisah dengan cuma amount-nya yang diubah, membiarkan aslinya tidak tersentuh? Bangun object baru yang menata isinya dengan spread syntax, seperti { ...order }. Yang layak dipastikan adalah hubungan antara apa yang kamu bangun dan aslinya.

const copiedOrder = { ...order }; membangun object terpisah, jadi order === copiedOrder adalah false. Untuk array dan object, === mengecek bukan apakah isinya cocok, tapi apakah keduanya persis 1 hal yang sama.

Untuk membandingkan isinya, tuliskan sendiri hal spesifik yang mau kamu bandingkan — jumlah key, value individual, dan sebagainya.

=== Tidak Melihat Isinya
backup = orderMenunjuk ke 1 halyang sama=== adalah true{ ...order }Membangun halyang terpisah=== adalah falseIsinyaidentik=== tidakmelihat isinyaTetap false
Itu true saat keduanya menunjuk ke hal yang sama, dan false untuk yang dibangun terpisah, bahkan dengan isi yang identik.
const cart = ["A4 Notebook"];
const copiedCart = [...cart];

// Hal yang terpisah, jadi === adalah false
console.log(cart === copiedCart);        // false

// Bahkan dengan isi identik, tetap false
console.log(["A"] === ["A"]);            // false

// Bandingkan isinya sendiri, dan itu true
console.log(cart[0] === copiedCart[0]);  // true

// Cuma true saat keduanya menunjuk ke hal yang sama
const savedCart = cart;
console.log(cart === savedCart);         // true

Bangun order terpisah dengan cuma amount-nya yang diubah, membiarkan aslinya tidak tersentuh. order sudah dideklarasikan.

① Bangun object terpisah yang meng-copy isi order, tulis ulang total-nya jadi 0, dan tampilkan.

② Tampilkan total order aslinya.

③ Tampilkan apakah order dan object dari ① adalah hal yang sama.

④ Tampilkan apakah keduanya punya jumlah key yang sama.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Copying Tidak Mencapai Value Nested — Shallow vs Deep Copy

Spread syntax sejauh ini sudah menghasilkan copy yang aman untuk value primitif seperti amount. Tapi copy sebuah order dengan object nested, seperti data customer, dan meski object terluarnya jadi terpisah, yang di dalamnya masih menunjuk ke hal yang persis sama seperti aslinya. Perbaiki sebuah address di apa yang kamu kira copy-nya, dan address order aslinya ikut berubah — ketidakcocokan yang tidak kamu duga.

Yang dibangun spread syntax adalah shallow copy (copy yang cuma menduplikasi property level pertama, membiarkan object nested apa pun menunjuk ke hal yang sama). Value di level pertama jadi independen kalau mereka primitive, tapi saat yang di dalamnya adalah object atau array, informasi lokasinya — reference-nya — cuma di-copy apa adanya.

Saat kamu perlu bagian dalamnya dibangun ulang juga, pakai deep copy (copy yang membangun ulang semuanya, sampai ke value nested). Tulis structuredClone(order), dan tiap level nesting dibangun ulang sebagai object terpisah.

Itu function yang disediakan browser, jadi berfungsi langsung di console situs ini juga. Cuma tidak berfungsi pada object yang berisi function.

Seberapa Dalam Sebuah Copy Sebenarnya Mencapai
{ ...order }Bangun ulang cumalevel pertamaorderId jadiindependencustomeryang nestedReference-nyacuma ikut di-copyUbah, dan aslinyaikut berubahstructuredCloneBangun ulang sampaike dalamUbah, aslinyatetap aman
Spread syntax cuma membangun ulang level pertama. Pakai structuredClone saat kamu perlu bagian dalamnya independen juga.
const order = {
  orderId: "ORD-1342",
  customer: { name: "Kenji Ross", city: "Denver" },
};

// Shallow copy: level pertama terpisah, bagian dalamnya masih menunjuk ke hal yang sama
const shallow = { ...order };
shallow.orderId = "ORD-9999";
console.log(order.orderId);                      // ORD-1342 (independen)

shallow.customer.city = "Boulder";
console.log(order.customer.city);                // Boulder (aslinya ikut berubah)
console.log(order.customer === shallow.customer);  // true

// Deep copy: bahkan bagian dalamnya jadi terpisah
const deep = structuredClone(order);
deep.customer.city = "Aurora";
console.log(order.customer.city);                // Boulder (aslinya aman)
console.log(order.customer === deep.customer);   // false

Copy sebuah order yang punya data customer nested di dalamnya, dan lihat seberapa jauh sebuah tulis-ulang mencapai. order sudah dideklarasikan.

① Bangun copy dengan spread syntax, tulis ulang kota di copy-nya jadi "Tacoma," lalu tampilkan kota order aslinya.

② Tampilkan apakah customer aslinya dan copy-nya adalah hal yang sama.

③ Bangun copy terpisah yang membangun ulang sampai ke dalam, tulis ulang kotanya jadi "Renton," lalu tampilkan kota order aslinya.

④ Tampilkan apakah customer aslinya dan copy dari ③ adalah hal yang sama.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Menghentikan Tulis-Ulang — Object.freeze

Seperti yang sudah kamu lihat, array dan object bisa berubah tanpa sengaja cuma karena berbagi reference yang sama. Beberapa value, seperti default settings atau tax rate, butuh perlindungan yang beda dari const — kamu nggak mau itu ditulis ulang belakangan. const saja tidak menghentikan isinya ditulis ulang, jadi kamu perlu cara menandai object itu sendiri terlarang untuk diubah.

Tulis Object.freeze(settings), dan object itu berhenti menerima perubahan, penambahan, atau penghapusan pada property-nya. Coba assign, tambah, atau hapus sesuatu setelah membekukannya, dan itu melempar TypeError tepat di situ, menghentikan eksekusi.

Cek apakah sesuatu beku dengan Object.isFrozen(settings). Pembekuan cuma mencapai level pertama, jadi object nested perlu dibekukan terpisah.

Tulis-Ulang Setelah Membekukan Berhenti dengan TypeError
Assignment sebelummembekukanDiterimaseperti biasaJadi valuebaruAssignment setelahmembekukanMelemparTypeErrorEksekusiberhenti di situTambah/hapus setelahmembekukanMelemparTypeErrorEksekusiberhenti di situ
Begitu dibekukan, assignment, penambahan, dan penghapusan semuanya gagal. TypeError langsung muncul di situ, dan tidak ada apa pun setelah baris itu yang berjalan.
const settings = { theme: "dark" };

// Bisa ditulis ulang sebelum dibekukan
settings.theme = "light";
console.log(settings.theme);             // light

Object.freeze(settings);
console.log(Object.isFrozen(settings));  // true

// Meng-assign setelah dibekukan berhenti di baris itu
// settings.theme = "dark";
// TypeError: Cannot assign to read only property 'theme' of object '#<Object>'

// Pembekuan cuma mencapai level pertama — bagian dalamnya tidak berhenti
const nested = Object.freeze({ display: { theme: "dark" } });
nested.display.theme = "light";
console.log(nested.display.theme);       // light

// Untuk mengubahnya, bangun object baru sebagai gantinya
const updated = { ...settings, theme: "dark" };
console.log(updated.theme);              // dark
console.log(settings.theme);             // light (aslinya aman)

Untuk Mengubahnya, Bangun Object Baru

Meng-assign, menambah, atau menghapus pada object beku melempar TypeError, dan tidak ada apa pun setelah baris itu yang berjalan. Untuk mengubah object beku, jangan tulis ulang — bangun yang baru sebagai gantinya, seperti { ...settings, theme: "light" }.

Kode di bawah mencoba menulis ulang rate settings yang beku setelahnya. Jalankan apa adanya dan cek sejauh mana itu berjalan dan message apa yang menghentikannya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Tulis const backup = order;, lalu jalankan backup.total = 0;. Apa yang terjadi pada order.total?

Soal 2Dengan const a = ["A"]; const b = ["A"];, apa yang dikembalikan a === b?

Soal 3Copy data dengan object nested memakai { ...order }. Apa yang terjadi pada object yang nested itu?