Dalam bahasa pemrograman apa pun, ada tipe data yang dideskripsikan oleh pemrogram ke subjek untuk pekerjaan lebih lanjut dan, jika perlu, memprosesnya. JavaScript bukan pengecualian, ia memiliki tipe data primitif (
Number
,
String
,
Boolean
,
Symbol
, dll.) Dan referensi (
Array
,
Object
,
Function
,
Maps
,
Sets
, dll.). Perlu dicatat bahwa tipe data primitif tidak dapat diubah - nilainya tidak dapat dimodifikasi, tetapi hanya dapat ditimpa dengan nilai penuh baru, tetapi dengan tipe data referensi, yang terjadi adalah sebaliknya. Sebagai contoh, deklarasikan variabel tipe
Number
dan
Object
:
let num = 5; let obj = { a: 5 };
Kami tidak dapat mengubah variabel
num
, kami hanya dapat menulis ulang nilainya, tetapi kami dapat memodifikasi variabel obj:
let num = 10; let obj = { a: 5, b: 6 };
Seperti yang Anda lihat, dalam kasus pertama kita menimpa nilai variabel, dan yang kedua kita memperluas objek. Dari sini kami menyimpulkan bahwa tipe data primitif tidak dapat diperluas, dan dengan tipe data referensi kami dapat melakukan ini, bahkan dengan pengubah
const
.
Yang terakhir dapat dibekukan, misalnya, menggunakan
Object.freeze(obj)
, tetapi topik ini berada di luar cakupan artikel (tautan untuk
Object.defineProperty yang ingin tahu,
melindungi objek dari perubahan ).
Bagaimana tipe data diteruskan ke fungsi dalam JavaScript? Setiap programmer js mungkin akan dengan mudah menjawab pertanyaan ini, namun demikian, katakanlah: tipe data primitif selalu diteruskan ke fungsi hanya berdasarkan nilai, dan yang direferensikan selalu hanya dengan referensi. Dan di sini dengan yang terakhir, dalam beberapa situasi, masalah muncul.
Mari kita lihat sebuah contoh:
const arr = [0, 1, 2, 3, 4, 5]; console.log("Array: ", arr);
Dalam hal ini, kami cukup mendeklarasikan array angka dan menampilkannya di konsol. Sekarang kita meneruskannya ke fungsi yang mengembalikan array baru, tetapi dengan penambahan beberapa nilai dalam argumen kedua, ke akhir array baru:
const arr = [0, 1, 2, 3, 4, 5]; console.log("Old array: ", arr);
Seperti yang bisa kita lihat dari kesimpulan konsol, tidak hanya array baru telah berubah, tetapi juga yang lama. Ini terjadi karena dalam fungsi
insertValToArr
kami baru saja menetapkan satu array ke
const newArr = arr
lain
const newArr = arr
, dan oleh karena itu membuat tautan ke array yang ada dan ketika kami mencoba memodifikasi array baru, itu merujuk ke area memori dari array lama dan, secara kasar, mengubahnya. Dan karena kedua array merujuk ke area memori yang sama, mereka akan memiliki nilai yang sama. Mari kita ubah fungsi kita sehingga tidak bisa mengubah array lama:
const arr = [0, 1, 2, 3, 4, 5]; const newArr = insertValToArr(arr, 15); console.log("New array: ", newArr);
Array lama tidak berubah, karena kami menerima masing-masing elemen dan secara individual menetapkan nilai elemen ke elemen array baru. Sekarang yang terakhir memiliki area memori yang terpisah dan jika Anda mengubahnya, maka ini tidak akan mempengaruhi array yang lama. Tetapi semua ini adalah contoh sederhana dan dalam program nyata, kemungkinan besar tidak hanya array satu dimensi yang akan ditemui, tetapi juga dua dimensi, lebih jarang tiga dimensi, dan bahkan lebih jarang empat dimensi. Kebanyakan mereka ditemukan dalam bentuk array asosiatif (tabel hash). Dalam JavaScript, ini adalah objek yang paling sering.
Mari kita lihat metode standar untuk menyalin objek yang disediakan JavaScript - Object.assign () digunakan untuk menyalin nilai semua properti enumerasinya sendiri dari satu atau lebih objek sumber ke objek target. Setelah menyalin, ia mengembalikan objek target. Pertimbangkan itu:
const obj = { a: 1 }; const newObj = Object.assign({}, obj); console.log(newObj);
Dan lagi masalah lama, kita merujuk ke area memori yang sama, yang mengarah pada modifikasi dua objek sekaligus - mengubah satu akan mengubah yang lain. Apa yang harus dilakukan jika kita perlu mendapatkan salinan objek yang kompleks (dengan banyak bercabang) dan pada saat yang sama, mengubah objek, jangan memodifikasi yang lain? Untuk menjawab pertanyaan ini adalah tujuan dari artikel ini. Selanjutnya kita akan mempertimbangkan bagaimana menulis metode kita sendiri untuk kloning mendalam (menyalin) objek dari cabang apa pun. Mari kita mulai ke kode.
Langkah 1: mendeklarasikan dan menginisialisasi objek
Z
, dan juga membuat output konsol untuk perbandingan sebelum dan setelah kloning:
const Z = { a: 5, b: { g: 8, y: 9, t: { q: 48 } }, x: 47, l: { f: 85, p: { u: 89, m: 7 }, s: 71 }, r: { h: 9, a: 'test', s: 'test2' } }; console.log('Z object before cloning: ', Z);

Langkah 2: tetapkan objek
Z
ke objek
refToZ
untuk menunjukkan perbedaan antara penugasan normal dan kloning mendalam:
const refToZ = Z;
Langkah 3: tetapkan objek
Z
objek
Y
menggunakan fungsi
deepClone
dan tambahkan properti baru ke objek
Y
Setelah itu, tampilkan dua objek ini di konsol:
const Y = deepClone(Z); function deepClone(obj) { const clObj = {}; for(const i in obj) { if (obj[i] instanceof Object) { clObj[i] = deepClone(obj[i]); continue; } clObj[i] = obj[i]; } return clObj; } Y.addnlProp = { fd: 45 }; console.log('Z object after cloning: ', Z); console.log('Y object: ', Y);


Di konsol, kita melihat dengan jelas bahwa mengubah objek
Y
, menambahkan properti baru, kita tidak mengubah objek
Z
dan yang terakhir tidak akan memiliki properti
addnlProp
di tubuhnya.
Langkah 4: ubah properti
x
, yang ada di tubuh objek
Z
dan
Y
dan sekali lagi tampilkan kedua objek di konsol:
Yx = 76; console.log('Y object: ', Y); console.log('Z object: ', Z);


Dengan mengubah properti yang sama di objek
Y
, kami tidak memengaruhi properti di tubuh
Z
Langkah 5: pada langkah terakhir, untuk perbandingan, kami cukup menambahkan properti
addToZ
dengan nilai 100 ke objek
refToZ
dan menampilkan ketiga objek di konsol:
refToZ.addToZ = 100; console.log('refToZ object: ', refToZ); console.log('Z object: ', Z); console.log('Y object: ', Y);



Dengan mengubah objek
refToZ
kami juga mengubah
Z
, tetapi
Y
tidak terpengaruh. Dari sini kami menyimpulkan bahwa fungsi kami membuat objek baru yang independen dengan properti dan nilainya dari objek yang ada (kode untuk mengimplementasikan fungsi
deepClone
dapat ditemukan di
CodePen ).
Mari kita membahas implementasi fungsi ini. Yang terakhir menemukan sarang objek, tanpa menyadarinya. Bagaimana dia melakukan ini? Masalahnya adalah bahwa dalam hal ini kita menggunakan algoritma yang terkenal untuk grafik - pencarian mendalam. Objek adalah grafik yang memiliki satu atau banyak cabang, yang pada gilirannya dapat memiliki cabang mereka, dll. Agar kita dapat menemukan semuanya, kita harus masuk ke setiap cabang dan bergerak ke kedalamannya, jadi kita akan menemukan setiap simpul dalam grafik dan mendapatkan nilainya. Pencarian mendalam dapat diimplementasikan dalam 2 cara: rekursi dan menggunakan loop. Yang kedua mungkin berubah menjadi lebih cepat, karena itu tidak akan mengisi tumpukan panggilan, yang pada gilirannya melakukan rekursi. Dalam implementasi fungsi
deepClone
kami
deepClone
kami menggunakan kombinasi rekursi dengan loop. Jika Anda ingin membaca buku tentang algoritma, saya menyarankan Anda untuk memulai Aditya Bhargava "Algoritma Grokayem" atau Thomas Kormen "Algoritma: konstruksi dan analisis" yang lebih mendalam.
Untuk meringkas, kami mengingat tipe data dalam JavaScript dan bagaimana mereka diteruskan ke fungsi. Dianggap contoh sederhana kloning independen dari array satu dimensi yang sederhana. Kami mempertimbangkan salah satu implementasi bahasa standar untuk menyalin objek dan sebagai hasilnya menulis fungsi kecil (dalam ukuran) untuk kloning mendalam independen dari objek kompleks. Fungsi serupa dapat menemukan aplikasinya baik di sisi server (Node js), yang lebih mungkin, maupun pada klien. Saya harap artikel ini bermanfaat bagi Anda. Sampai jumpa lagi.