Seperti yang kita ketahui dari bab Pengumpulan Sampah, mesin JavaScript menyimpan nilai di memori saat nilai tersebut “dapat dijangkau” dan berpotensi digunakan.
Misalnya:
biarkan john = { nama: "John" }; // objek dapat diakses, john adalah referensinya // menimpa referensi john = batal; // objek akan dihapus dari memori
Biasanya, properti suatu objek atau elemen array atau struktur data lainnya dianggap dapat dijangkau dan disimpan di memori saat struktur data tersebut berada di memori.
Contohnya, jika kita memasukkan sebuah objek ke dalam sebuah array, maka selama array tersebut hidup, objek tersebut juga akan tetap hidup, meskipun tidak ada referensi lain ke dalamnya.
Seperti ini:
biarkan john = { nama: "John" }; biarkan array = [ john ]; john = batal; // menimpa referensi // objek yang sebelumnya direferensikan oleh john disimpan di dalam array // oleh karena itu, sampah tidak akan dikumpulkan // kita bisa mendapatkannya sebagai array[0]
Mirip dengan itu, jika kita menggunakan sebuah objek sebagai kunci dalam Map
biasa, maka ketika Map
tersebut ada, maka objek tersebut juga akan ada. Ini menempati memori dan mungkin bukan sampah yang dikumpulkan.
Misalnya:
biarkan john = { nama: "John" }; biarkan peta = Peta baru(); peta.set(john, "..."); john = batal; // menimpa referensi // john disimpan di dalam peta, // kita bisa mendapatkannya dengan menggunakan map.keys()
WeakMap
pada dasarnya berbeda dalam aspek ini. Itu tidak mencegah pengumpulan sampah pada objek-objek utama.
Mari kita lihat apa artinya ini dengan contoh.
Perbedaan pertama antara Map
dan WeakMap
adalah kunci harus berupa objek, bukan nilai primitif:
biarkan WeakMap = new WeakMap(); misalkan obj = {}; lemahMap.set(obj, "ok"); // berfungsi dengan baik (kunci objek) // tidak bisa menggunakan string sebagai kuncinya lemahMap.set("test", "Ups"); // Kesalahan, karena "test" bukan sebuah objek
Sekarang, jika kita menggunakan suatu objek sebagai kunci di dalamnya, dan tidak ada referensi lain ke objek tersebut – maka objek tersebut akan dihapus dari memori (dan dari peta) secara otomatis.
biarkan john = { nama: "John" }; biarkan WeakMap = new WeakMap(); lemahMap.set(john, "..."); john = batal; // menimpa referensi // john dihapus dari ingatan!
Bandingkan dengan contoh Map
biasa di atas. Sekarang jika john
hanya ada sebagai kunci WeakMap
– maka akan otomatis terhapus dari peta (dan memori).
WeakMap
tidak mendukung iterasi dan metode keys()
, values()
, entries()
, jadi tidak ada cara untuk mendapatkan semua kunci atau nilai darinya.
WeakMap
hanya memiliki metode berikut:
weakMap.set(key, value)
weakMap.get(key)
weakMap.delete(key)
weakMap.has(key)
Mengapa ada batasan seperti itu? Itu karena alasan teknis. Jika suatu objek telah kehilangan semua referensi lainnya (seperti john
pada kode di atas), maka objek tersebut akan dikumpulkan sampahnya secara otomatis. Namun secara teknis tidak ditentukan secara pasti kapan pembersihan terjadi .
Mesin JavaScript memutuskan hal itu. Ia mungkin memilih untuk melakukan pembersihan memori segera atau menunggu dan melakukan pembersihan nanti ketika penghapusan lebih banyak terjadi. Jadi, secara teknis, jumlah elemen WeakMap
saat ini tidak diketahui. Mesin mungkin sudah membersihkannya atau belum, atau melakukannya sebagian. Oleh karena itu, metode yang mengakses semua kunci/nilai tidak didukung.
Sekarang, di mana kita memerlukan struktur data seperti itu?
Area utama penerapan WeakMap
adalah penyimpanan data tambahan .
Jika kita bekerja dengan objek yang “milik” kode lain, bahkan mungkin perpustakaan pihak ketiga, dan ingin menyimpan beberapa data yang terkait dengannya, yang seharusnya hanya ada saat objek tersebut masih hidup – maka WeakMap
adalah jawabannya. diperlukan.
Kita memasukkan datanya ke WeakMap
, menggunakan objek sebagai kuncinya, dan ketika objek tersebut dikumpulkan sampahnya, otomatis data tersebut juga akan hilang.
lemahMap.set(john, "dokumen rahasia"); // jika John meninggal, dokumen rahasia akan otomatis dimusnahkan
Mari kita lihat sebuah contoh.
Misalnya, kami memiliki kode yang mencatat jumlah kunjungan pengguna. Informasinya disimpan dalam peta: objek pengguna adalah kuncinya dan jumlah kunjungan adalah nilainya. Ketika pengguna keluar (objeknya mengumpulkan sampah), kami tidak ingin menyimpan jumlah kunjungan mereka lagi.
Berikut contoh fungsi penghitungan dengan Map
:
// ? kunjunganCount.js biarkan visitsCountMap = Peta baru(); // peta: pengguna => jumlah kunjungan // menambah jumlah kunjungan fungsi countUser(pengguna) { biarkan menghitung = kunjunganCountMap.get(pengguna) || 0; visitsCountMap.set(pengguna, hitungan + 1); }
Dan inilah bagian kode lainnya, mungkin file lain yang menggunakannya:
// ? main.js biarkan john = { nama: "John" }; countUser(john); // hitung kunjungannya // nanti John meninggalkan kita john = batal;
Sekarang, objek john
seharusnya dikumpulkan dari sampah, tetapi tetap berada di memori, karena merupakan kunci dalam visitsCountMap
.
Kita perlu membersihkan visitsCountMap
ketika kita menghapus pengguna, jika tidak, maka memori akan bertambah tanpa batas. Pembersihan seperti itu bisa menjadi tugas yang membosankan dalam arsitektur yang kompleks.
Kita dapat menghindarinya dengan beralih ke WeakMap
:
// ? kunjunganCount.js biarkan visitsCountMap = new WeakMap(); // peta lemah: pengguna => jumlah kunjungan // menambah jumlah kunjungan fungsi countUser(pengguna) { biarkan menghitung = kunjunganCountMap.get(pengguna) || 0; visitsCountMap.set(pengguna, hitungan + 1); }
Sekarang kita tidak perlu membersihkan visitsCountMap
. Setelah objek john
menjadi tidak dapat dijangkau, kecuali sebagai kunci WeakMap
, objek tersebut akan dihapus dari memori, bersama dengan informasi dari kunci tersebut dari WeakMap
.
Contoh umum lainnya adalah caching. Kita dapat menyimpan (“cache”) hasil dari suatu fungsi, sehingga panggilan selanjutnya pada objek yang sama dapat menggunakannya kembali.
Untuk mencapainya, kita bisa menggunakan Map
(bukan skenario optimal):
// ? cache.js biarkan cache = Peta baru(); // hitung dan ingat hasilnya proses fungsi(obj) { if (!cache.has(obj)) { let result = /* perhitungan hasil untuk */ obj; cache.set(obj, hasil); hasil pengembalian; } kembalikan cache.get(obj); } // Sekarang kita menggunakan proses() di file lain: // ? main.js let obj = {/* misalkan kita mempunyai objek */}; biarkan hasil1 = proses(obj); // dihitung // ...nanti, dari tempat lain dalam kode... biarkan hasil2 = proses(obj); // mengingat hasil yang diambil dari cache // ...nanti, ketika objek tersebut tidak diperlukan lagi: obj = nol; alert(cache.ukuran); // 1 (Aduh! Objek masih dalam cache, memakan memori!)
Untuk beberapa panggilan process(obj)
dengan objek yang sama, ia hanya menghitung hasilnya pertama kali, lalu mengambilnya dari cache
. Kelemahannya adalah kita perlu membersihkan cache
ketika objek tersebut tidak diperlukan lagi.
Jika kita mengganti Map
dengan WeakMap
, maka masalah ini akan hilang. Hasil cache akan dihapus dari memori secara otomatis setelah objek dikumpulkan sampahnya.
// ? cache.js biarkan cache = new WeakMap(); // hitung dan ingat hasilnya proses fungsi(obj) { if (!cache.has(obj)) { biarkan hasil = /* hitung hasil untuk */ obj; cache.set(obj, hasil); hasil pengembalian; } kembalikan cache.get(obj); } // ? main.js misalkan obj = {/* suatu objek */}; biarkan hasil1 = proses(obj); biarkan hasil2 = proses(obj); // ...nanti, ketika objek tersebut tidak diperlukan lagi: obj = nol; // Tidak bisa mendapatkan cache.size, karena ini adalah WeakMap, // tapi sekarang 0 atau sebentar lagi akan menjadi 0 // Saat obj mengumpulkan sampah, data cache juga akan dihapus
WeakSet
berperilaku serupa:
Ini analog dengan Set
, tetapi kita hanya dapat menambahkan objek ke WeakSet
(bukan primitif).
Sebuah objek ada di himpunan sementara objek tersebut dapat dijangkau dari tempat lain.
Seperti Set
, ia mendukung add
, has
dan delete
, tetapi tidak mendukung size
, keys()
dan tidak ada iterasi.
Karena “lemah”, ini juga berfungsi sebagai penyimpanan tambahan. Namun bukan karena data sembarangan, melainkan karena fakta “ya/tidak”. Keanggotaan di WeakSet
dapat berarti sesuatu tentang objek tersebut.
Misalnya, kami dapat menambahkan pengguna ke WeakSet
untuk melacak mereka yang mengunjungi situs kami:
biarkan dikunjungiSet = new WeakSet(); biarkan john = { nama: "John" }; biarkan pete = { nama: "Pete" }; misalkan mary = { nama: "Maria" }; mengunjungiSet.add(john); // John mengunjungi kami mengunjungiSet.add(pete); // Lalu Pete mengunjungiSet.add(john); // John lagi // VisitSet mempunyai 2 pengguna sekarang // periksa apakah John berkunjung? alert(visitedSet.has(john)); // BENAR // periksa apakah Mary berkunjung? alert(visitedSet.has(mary)); // PALSU john = batal; // VisitSet akan dibersihkan secara otomatis
Keterbatasan yang paling menonjol dari WeakMap
dan WeakSet
adalah tidak adanya iterasi, dan ketidakmampuan untuk mendapatkan semua konten saat ini. Hal ini mungkin tampak merepotkan, namun tidak menghalangi WeakMap/WeakSet
melakukan tugas utamanya – menjadi penyimpanan data “tambahan” untuk objek yang disimpan/dikelola di tempat lain.
WeakMap
adalah kumpulan mirip Map
yang hanya mengizinkan objek sebagai kunci dan menghapusnya bersama dengan nilai terkait setelah objek tersebut tidak dapat diakses dengan cara lain.
WeakSet
adalah kumpulan mirip Set
yang hanya menyimpan objek dan menghapusnya setelah tidak dapat diakses dengan cara lain.
Keuntungan utamanya adalah memiliki daya rekat yang lemah terhadap objek, sehingga dapat dengan mudah dikeluarkan oleh pemulung.
Hal ini mengakibatkan tidak adanya dukungan untuk clear
, size
, keys
, values
…
WeakMap
dan WeakSet
digunakan sebagai struktur data “sekunder” selain penyimpanan objek “utama”. Setelah objek dihapus dari penyimpanan utama, jika objek tersebut hanya ditemukan sebagai kunci WeakMap
atau di WeakSet
, objek tersebut akan dibersihkan secara otomatis.
pentingnya: 5
Ada serangkaian pesan:
biarkan pesan = [ {teks: "Halo", dari: "John"}, {teks: "Bagaimana?", dari: "John"}, {teks: "Sampai jumpa", dari: "Alice"} ];
Kode Anda dapat mengaksesnya, tetapi pesannya dikelola oleh kode orang lain. Pesan baru ditambahkan, pesan lama dihapus secara berkala dengan kode itu, dan Anda tidak tahu kapan tepatnya hal itu terjadi.
Sekarang, struktur data manakah yang dapat Anda gunakan untuk menyimpan informasi apakah pesan “telah dibaca”? Strukturnya harus sesuai untuk memberikan jawaban “apakah sudah dibaca?” untuk objek pesan yang diberikan.
PS Ketika sebuah pesan dihapus dari messages
, pesan itu juga akan hilang dari struktur Anda.
PPS Kita tidak boleh mengubah objek pesan, menambahkan properti kita ke objek tersebut. Karena dikelola oleh kode orang lain, hal itu dapat menimbulkan konsekuensi buruk.
Mari kita simpan pesan yang sudah dibaca di WeakSet
:
biarkan pesan = [ {teks: "Halo", dari: "John"}, {teks: "Bagaimana?", dari: "John"}, {teks: "Sampai jumpa", dari: "Alice"} ]; biarkan readMessages = new WeakSet(); // dua pesan telah dibaca readMessages.add(pesan[0]); readMessages.add(pesan[1]); // readMessages memiliki 2 elemen // ...mari kita baca pesan pertama lagi! readMessages.add(pesan[0]); // readMessages masih memiliki 2 elemen unik // jawaban: apakah pesan[0] sudah dibaca? alert("Baca pesan 0: " + readMessages.has(pesan[0])); // BENAR pesan.shift(); // sekarang readMessages memiliki 1 elemen (secara teknis memori dapat dibersihkan nanti)
WeakSet
memungkinkan untuk menyimpan sekumpulan pesan dan dengan mudah memeriksa keberadaan pesan di dalamnya.
Itu membersihkan dirinya sendiri secara otomatis. Kerugiannya adalah kita tidak bisa mengulanginya, tidak bisa mendapatkan “semua pesan yang sudah dibaca” darinya secara langsung. Tapi kita bisa melakukannya dengan mengulangi semua pesan dan memfilter pesan yang ada di kumpulan.
Solusi lain yang berbeda adalah dengan menambahkan properti seperti message.isRead=true
ke pesan setelah dibaca. Karena objek pesan dikelola oleh kode lain, hal ini umumnya tidak disarankan, namun kita dapat menggunakan properti simbolik untuk menghindari konflik.
Seperti ini:
// properti simbolik hanya diketahui oleh kode kita biarkan isRead = Simbol("isRead"); pesan[0][isRead] = benar;
Sekarang kode pihak ketiga mungkin tidak akan melihat properti tambahan kami.
Meskipun simbol memungkinkan untuk menurunkan kemungkinan masalah, menggunakan WeakSet
lebih baik dari sudut pandang arsitektur.
pentingnya: 5
Ada serangkaian pesan seperti pada tugas sebelumnya. Situasinya serupa.
biarkan pesan = [ {teks: "Halo", dari: "John"}, {teks: "Bagaimana?", dari: "John"}, {teks: "Sampai jumpa", dari: "Alice"} ];
Pertanyaannya sekarang adalah: struktur data manakah yang Anda sarankan untuk menyimpan informasi: “kapan pesan dibaca?”.
Pada tugas sebelumnya kita hanya perlu menyimpan fakta “ya/tidak”. Sekarang kita perlu menyimpan tanggalnya, dan tanggal tersebut hanya akan tersimpan di memori sampai pesan tersebut dikumpulkan.
Tanggal PS dapat disimpan sebagai objek kelas Date
bawaan, yang akan kita bahas nanti.
Untuk menyimpan tanggal, kita dapat menggunakan WeakMap
:
biarkan pesan = [ {teks: "Halo", dari: "John"}, {teks: "Bagaimana?", dari: "John"}, {teks: "Sampai jumpa", dari: "Alice"} ]; biarkan readMap = new WeakMap(); readMap.set(pesan[0], Tanggal baru(2017, 1, 1)); // Objek tanggal yang akan kita pelajari nanti