15 Desember 2021

Referensi objek dan menyalinnya

Salah satu perbedaan mendasar dari objek versus primitif adalah bahwa objek disimpan dan disalin ???dengan referensi???, sedangkan nilai primitif: string, angka, boolean, dll ??? selalu disalin ???sebagai nilai keseluruhan???.

Itu mudah dipahami jika kita melihat sedikit ke belakang tentang apa yang terjadi saat kita menyalin sebuah nilai.

Mari kita mulai dengan yang primitif, seperti string.

Di sini kami memasukkan salinan pesan ke dalamfrase:

let message = "Hello!";
let phrase = message;

Sebagai hasilnya kita punya dua variabel yang berdiri sendiri, dan keduanya menyimpan nilai string "Hello!".

Hasil yang cukup jelas, bukan?

Objek tidak seperti itu.

Sebuah variabel tidak menyimpan objek itu sendiri, akan tetapi ???disimpan didalam memori???, dengan kata lain ???mereferensi??? kepadanya (ke data didalam memori).

Mari kita lihat contoh variabel tersebut:

let user = {
  name: "John"
};

Dan ini bagaimana kita menyimpannya di dalam memory:

Objek disimpan di suatu tempat di memori (di sebelah kanan gambar), sedangkan variabel user (di sebelah kiri) memiliki" referensi "padanya.

Kita mungkin menganggap variabel objek, seperti pengguna, 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 itu dan melakukan operasi pada objek sebenarnya.

Sekarang inilah mengapa itu penting.

Ketika sebuah variabel objek disalin ??? referensinya akan tersalin, objeknya tidak terduplikasi.

Contoh:

let user = { name: "John" };

let admin = user; // menyalin referensinya

Kita sekarang punya dua variabel, masing-masing mereferensi ke objek yang sama:

Seperti yang Anda lihat, masih ada satu objek, sekarang dengan dua variabel yang mereferensikannya.

Kita dapat menggunakan variabel apa saja untuk mengakses objek dan mengubah isinya:

let user = { name: 'John' };

let admin = user;

admin.name = 'Pete'; // mengganti admin dengan menggunakan "referensi"

alert(user.name); // 'Pete', perubahan akan terlihat pada "user"

Seolah-olah kita memiliki lemari dengan dua kunci dan menggunakan salah satunya (admin) untuk masuk ke dalamnya dan membuat perubahan. Kemudian, jika nanti kita menggunakan kunci lain (pengguna), kita masih membuka lemari yang sama dan dapat mengakses konten yang diubah.

Perbandingan dengan referensi

Dua objek adalah sama jika mereka objek yang sama.

Dibawah adalah dua variabel yang mereferensi ke objek yang sama, dengan demikian mereka sama:

Contohnya, di sini a dan b mereferensikan objek yang sama, sehingga keduanya sama:

let a = {};
let b = a; // menyalin referensi

alert( a == b ); // true, kedua variabel mereferensi ke objek yang sama
alert( a === b ); // true

Dan dibawah adalah dua objek yang berdiri sendiri, tidaklah sama, walaupun keduanya kosong:

let a = {};
let b = {}; // dua objek yang berdiri sendiri

alert( a == b ); // false

Untuk perbandingan seperti obj1 > obj2 atau untuk perbandingan dengan sebuah nilai primitif obj == 5, objek akan diubah dahulu menjadi primitif. Kita akan belajar bagaimana perubahan objek sebentar lagi, akan tetapi sebenarnya, perbandingan seperti itu muncul sangat jarang, biasanya hanya sebuah hasil dari kesalahan koding.

Penggandaan dan penggabungan, Object.assign

Jadi, menyalin sebuah variabel objek akan menciptakan satu lagi referensi kepada objek yang sama.

Tapi bagaimana jika kita butuh untuk menduplikasi objek? Membuat salinan yang berdiri sendiri, menggandakan atau meng-klon?

Hal itu juga bisa dilakukan, tapi sedikit lebih sulit, karena tidak ada method bawaan untuk hal itu di javascript. Sebenarnya, hal itu juga jarang dibutuhkan. Di kebanyakan waktu, menyalin referensinya sudah cukup.

Tapi bagaimana jika kita benar-benar ingin hal itu, lalu kita membutuhkan untuk menciptakan sebuah objek dan mengulangi struktur dari objek yang sama dengan meng-iterasi propertinya dan menyalin mereka didalam level primitif.

Seperti ini:

let user = {
  name: "John",
  age: 30
};

let clone = {}; // objek kosong baru

// salin semua properti user kedalamnya
for (let key in user) {
  clone[key] = user[key];
}

// sekarang clone adalah sebuah objek yang berdiri sendiri dengan konten yang sama
clone.name = "Pete"; // ubah data didalamnya

alert( user.name ); // masih John didalam objek yang asli

Juga kita bisa menggunakan method Object.assign untuk itu.

sintaksnya adalah:

Object.assign(dest, [src1, src2, src3...])
  • Argumen pertama dest adalah sebuah objek target.
  • Argumen selanjutnya src1, ..., srcN (bisa sebanyak yang dibutuhkan) adalah objek sumber.
  • Itu akan menyalin properti dari seluruh objek sumber src1, ..., srcN kedalam target dest. Dengan kata lain, properti dari semua argumen dimulai dari argumen kedua akan disalin kedalam object pertama.
  • Setelah pemanggilan akan mengembalikan dest.

Contoh, kita bisa menggunakan untuk menggabungkan beberapa objek menjadi satu:

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// menyalin seluruh properti dari permission1 dan permission2 kedalam user
Object.assign(user, permissions1, permissions2);

// sekarang user = { name: "John", canView: true, canEdit: true }

Jika nama dari properti yang disali sudah ada, propertinya akan ditimpa:

let user = { name: "John" };

Object.assign(user, { name: "Pete" });

alert(user.name); // sekarang user = { name: "Pete" }

Kita juga bisa menggunakan Object.assign untuk mengganti perulangan for...in untuk penggandaan yang sederhana.

let user = {
  name: "John",
  age: 30
};

let clone = Object.assign({}, user);

Kode diatas akan menyalin seluruh properti dari user kedalam objek yang kosong dan mengembalikan/me-return hasilnya.

Ada juga metode lain untuk mengkloning objek, mis. menggunakan sintaksis spread clone = {...user}, dibahas nanti dalam tutorial.

Nested cloning

Sampai sekarang kita telah berasumsi bahwa seluruh properti dari user adalah primitif. Tapi properti bisa di referensi ke objek lainnya. Apa yang harus dilakukan dengan mereka?

Like this:

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

alert( user.sizes.height ); // 182

Now it???s not enough to copy clone.sizes = user.sizes, because the user.sizes is an object, it will be copied by reference. So clone and user will share the same sizes: Sekarang hal itu tidak cukup untuk menyalin clone.sizes = user.sizes, karena user.sizes adalah sebuah objek, itu akan tersalin secara referensi. Jadi clone dan user akan berbagi objek yang sama:

Like this:

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, objek yang sama

// user dan clone akan berbagi objek yang sama
user.sizes.width++;       // ganti properti dari satu tempat
alert(clone.sizes.width); // 51, melihat hasilnya ditempat yang lain

Untuk membenarkan hal itu, kita harus menggunakan perulangan kloning yang memeriksa setip nilai dari user[key] dan, jika itu adalah sebuah objek, lalu duplikasi strukturnya juga. Hal itu dinamakan dengan ???deep cloning???.

We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance _.cloneDeep(obj) from the JavaScript library lodash.

Const objects can be modified

An important side effect of storing objects as references is that an object declared as const can be modified.

For instance:

const user = {
  name: "John"
};

user.name = "Pete"; // (*)

alert(user.name); // Pete

Sepertinya baris (*) akan menyebabkan kesalahan, tetapi sebenarnya tidak. Nilai dari user adalah konstan, itu harus selalu mereferensikan objek yang sama, tetapi properti dari objek tersebut bebas untuk berubah.

Dengan kata lain, const user memberikan kesalahan hanya jika kita mencoba menyeteluser = ...secara keseluruhan.

yang berarti, jika kita benar-benar perlu membuat properti objek konstan, itu juga mungkin, tetapi menggunakan metode yang sama sekali berbeda. Kita akan membahasnya di bab <info: property-descriptors>.

Ringkasan

objek dibuat dan disalin dengan menggunakan referensi. Dengan kata lain, sebuah variable menyimpan bukanlah ???nilai objek???, tapi sebuah ???referensi??? (address/alamat di memori) untuk nilainya. Jadi menyalin sebuah variabel atau memindahkannya sebagai fungsi argumen akan menyalin referensinya, bukan objeknya.

Semua operasi yang disalin dengan menggunakan referensi (seperti menambah/menghapus properti) dilakukan didalam satu objek yang sama.

Untuk membuat ???salinan asli??? (kloning) kita dapat menggunakan Object.assign untuk apa yang disebut ???shallow copy???(objek bersarang disalin dengan referensi) atau fungsi"deep cloning", seperti _.cloneDeep (obj).

Peta tutorial