Salah satu perbedaan mendasar antara objek dan primitif adalah bahwa objek disimpan dan disalin “dengan referensi”, sedangkan nilai primitif: string, angka, boolean, dll – selalu disalin “sebagai nilai keseluruhan”.
Itu mudah dimengerti jika kita melihat sedikit apa yang terjadi ketika kita menyalin suatu nilai.
Mari kita mulai dengan yang primitif, seperti string.
Di sini kami memasukkan salinan message
ke dalam phrase
:
biarkan pesan = "Halo!"; biarkan frase = pesan;
Hasilnya kita mempunyai dua variabel bebas, masing-masing menyimpan string "Hello!"
.
Hasil yang cukup jelas bukan?
Objeknya tidak seperti itu.
Variabel yang ditetapkan ke suatu objek tidak menyimpan objek itu sendiri, melainkan “alamat di memori” – dengan kata lain “referensi” ke objek tersebut.
Mari kita lihat contoh variabel tersebut:
biarkan pengguna = { nama: "Yohanes" };
Dan inilah cara sebenarnya disimpan dalam memori:
Objek disimpan di suatu tempat di memori (di sebelah kanan gambar), sedangkan variabel user
(di sebelah kiri) memiliki “referensi” ke sana.
Kita mungkin menganggap variabel objek, seperti user
, seperti selembar kertas dengan alamat objek di atasnya.
Saat kita melakukan tindakan dengan objek, misalnya mengambil properti user.name
, mesin JavaScript melihat apa yang ada di alamat tersebut dan melakukan operasi pada objek sebenarnya.
Inilah mengapa ini penting.
Ketika variabel objek disalin, referensinya disalin, tetapi objek itu sendiri tidak diduplikasi.
Misalnya:
biarkan pengguna = { nama: "John" }; biarkan admin = pengguna; // salin referensinya
Sekarang kita mempunyai dua variabel, masing-masing menyimpan referensi ke objek yang sama:
Seperti yang Anda lihat, masih ada satu objek, tapi sekarang dengan dua variabel yang mereferensikannya.
Kita dapat menggunakan salah satu variabel untuk mengakses objek dan mengubah isinya:
biarkan pengguna = { nama: 'John' }; biarkan admin = pengguna; admin.nama = 'Pete'; // diubah oleh referensi "admin". alert(nama pengguna); // 'Pete', perubahan dilihat dari referensi "pengguna".
Seolah-olah kita memiliki lemari dengan dua kunci dan menggunakan salah satunya ( admin
) untuk masuk ke dalamnya dan melakukan perubahan. Kemudian, jika nanti kita menggunakan kunci lain ( user
), kita masih membuka kabinet yang sama dan dapat mengakses konten yang diubah.
Dua benda dikatakan sama hanya jika keduanya merupakan benda yang sama.
Misalnya, di sini a
dan b
mereferensikan objek yang sama, sehingga keduanya setara:
misalkan a = {}; misalkan b = a; // salin referensinya waspada( a == b ); // benar, kedua variabel mereferensikan objek yang sama waspada( a === b ); // BENAR
Dan di sini dua objek independen tidak sama, meskipun keduanya mirip (keduanya kosong):
misalkan a = {}; misalkan b = {}; // dua objek independen waspada( a == b ); // PALSU
Untuk perbandingan seperti obj1 > obj2
atau untuk perbandingan terhadap primitif obj == 5
, objek diubah menjadi primitif. Kita akan segera mempelajari cara kerja konversi objek, namun sejujurnya, perbandingan seperti itu sangat jarang diperlukan – biasanya perbandingan tersebut muncul akibat kesalahan pemrograman.
Objek const dapat dimodifikasi
Efek samping penting dari menyimpan objek sebagai referensi adalah objek yang dideklarasikan sebagai const
dapat dimodifikasi.
Misalnya:
pengguna konstan = { nama: "Yohanes" }; pengguna.nama = "Pete"; // (*) alert(nama pengguna); // Pete
Tampaknya baris (*)
akan menyebabkan kesalahan, tetapi ternyata tidak. Nilai user
adalah konstan, ia harus selalu mereferensikan objek yang sama, namun properti objek tersebut bebas berubah.
Dengan kata lain, const user
memberikan kesalahan hanya jika kita mencoba menyetel user=...
secara keseluruhan.
Meskipun demikian, jika kita benar-benar perlu membuat properti objek konstan, hal ini juga mungkin dilakukan, namun menggunakan metode yang sangat berbeda. Kami akan menyebutkannya di bab Tanda dan deskriptor properti.
Jadi, menyalin variabel objek akan membuat satu referensi lagi ke objek yang sama.
Namun bagaimana jika kita perlu menduplikasi suatu objek?
Kita dapat membuat objek baru dan mereplikasi struktur objek yang sudah ada, dengan mengulangi propertinya dan menyalinnya pada tingkat primitif.
Seperti ini:
biarkan pengguna = { nama: "Yohanes", usia: 30 }; biarkan kloning = {}; // objek kosong yang baru // mari salin semua properti pengguna ke dalamnya untuk (biarkan memasukkan pengguna) { clone[kunci] = pengguna[kunci]; } // sekarang clone adalah objek yang sepenuhnya independen dengan konten yang sama clone.nama = "Pete"; // mengubah data di dalamnya alert( nama pengguna ); // masih John di objek aslinya
Kita juga bisa menggunakan metode Object.assign.
Sintaksnya adalah:
Objek.assign(tujuan, ...sumber)
Argumen pertama dest
adalah objek target.
Argumen selanjutnya adalah daftar objek sumber.
Ini menyalin properti semua objek sumber ke target dest
, dan kemudian mengembalikannya sebagai hasilnya.
Misalnya, kita memiliki objek user
, mari tambahkan beberapa izin ke dalamnya:
biarkan pengguna = { nama: "John" }; biarkan izin1 = { canView: true }; biarkan izin2 = { canEdit: true }; // menyalin semua properti dari izin1 dan izin2 ke pengguna Objek.penetapan(pengguna, izin1, izin2); // sekarang pengguna = { nama: "John", canView: true, canEdit: true } alert(nama pengguna); // Yohanes alert(pengguna.canView); // BENAR alert(pengguna.canEdit); // BENAR
Jika nama properti yang disalin sudah ada, maka akan ditimpa:
biarkan pengguna = { nama: "John" }; Objek.penetapan(pengguna, { nama: "Pete" }); alert(nama pengguna); // sekarang pengguna = { nama: "Pete" }
Kita juga bisa menggunakan Object.assign
untuk melakukan kloning objek sederhana:
biarkan pengguna = { nama: "Yohanes", usia: 30 }; biarkan clone = Object.assign({}, pengguna); alert(clone.nama); // Yohanes alert(clone.age); // 30
Di sini ia menyalin semua properti user
ke objek kosong dan mengembalikannya.
Ada juga metode lain untuk mengkloning suatu objek, misalnya menggunakan sintaksis spread clone = {...user}
, yang akan dibahas nanti di tutorial.
Hingga saat ini kami berasumsi bahwa semua properti user
bersifat primitif. Tapi properti bisa menjadi referensi ke objek lain.
Seperti ini:
biarkan pengguna = { nama: "Yohanes", ukuran: { tinggi: 182, lebar: 50 } }; peringatan( pengguna.ukuran.tinggi ); // 182
Sekarang tidak cukup hanya menyalin clone.sizes = user.sizes
, karena user.sizes
adalah sebuah objek, dan akan disalin dengan referensi, jadi clone
dan user
akan berbagi ukuran yang sama:
biarkan pengguna = { nama: "Yohanes", ukuran: { tinggi: 182, lebar: 50 } }; biarkan clone = Object.assign({}, pengguna); peringatan( pengguna.ukuran === klon.ukuran ); // benar, objek yang sama // ukuran berbagi pengguna dan klon pengguna.ukuran.lebar = 60; // mengubah properti dari satu tempat alert(clone.sizes.lebar); // 60, dapatkan hasil dari yang lain
Untuk memperbaikinya dan membuat user
dan clone
objek benar-benar terpisah, kita harus menggunakan loop kloning yang memeriksa setiap nilai user[key]
dan, jika itu adalah objek, maka replikasi strukturnya juga. Itu disebut “kloning mendalam” atau “kloning terstruktur”. Ada metode StructuredClone yang mengimplementasikan kloning mendalam.
Panggilan structuredClone(object)
mengkloning object
dengan semua properti bersarang.
Inilah cara kita dapat menggunakannya dalam contoh kita:
biarkan pengguna = { nama: "Yohanes", ukuran: { tinggi: 182, lebar: 50 } }; biarkan clone = StructuredClone(pengguna); peringatan( pengguna.ukuran === klon.ukuran ); // salah, objek berbeda // pengguna dan klon sama sekali tidak berhubungan sekarang pengguna.ukuran.lebar = 60; // mengubah properti dari satu tempat alert(clone.sizes.lebar); // 50, tidak berhubungan
Metode structuredClone
dapat mengkloning sebagian besar tipe data, seperti objek, array, nilai primitif.
Ini juga mendukung referensi melingkar, ketika properti objek mereferensikan objek itu sendiri (secara langsung atau melalui rantai atau referensi).
Misalnya:
biarkan pengguna = {}; // mari kita membuat referensi melingkar: // user.me mereferensikan pengguna itu sendiri pengguna.me = pengguna; biarkan clone = StructuredClone(pengguna); alert(clone.me === clone); // BENAR
Seperti yang Anda lihat, clone.me
mereferensikan clone
, bukan user
! Jadi referensi melingkar juga telah dikloning dengan benar.
Meskipun demikian, ada beberapa kasus ketika structuredClone
gagal.
Misalnya, ketika suatu objek memiliki properti fungsi:
// kesalahan terstrukturKlon({ f: fungsi() {} });
Properti fungsi tidak didukung.
Untuk menangani kasus rumit seperti itu kita mungkin perlu menggunakan kombinasi metode kloning, menulis kode khusus atau, agar tidak menemukan kembali roda, mengambil implementasi yang sudah ada, misalnya _.cloneDeep(obj) dari pustaka JavaScript lodash.
Objek ditugaskan dan disalin dengan referensi. Dengan kata lain, variabel tidak menyimpan “nilai objek”, tetapi “referensi” (alamat di memori) untuk nilai tersebut. Jadi menyalin variabel tersebut atau meneruskannya sebagai argumen fungsi akan menyalin referensi itu, bukan objek itu sendiri.
Semua operasi melalui referensi yang disalin (seperti menambah/menghapus properti) dilakukan pada objek tunggal yang sama.
Untuk membuat "salinan asli" (klon) kita dapat menggunakan Object.assign
untuk apa yang disebut "salinan dangkal" (objek bersarang disalin dengan referensi) atau fungsi "kloning dalam" structuredClone
atau menggunakan implementasi kloning khusus, seperti sebagai _.cloneDeep(obj).