Fitur bahasa yang “tersembunyi”.
Artikel ini membahas topik dengan fokus yang sangat sempit, yang jarang ditemui sebagian besar pengembang dalam praktiknya (dan bahkan mungkin tidak menyadari keberadaannya).
Kami menyarankan untuk melewatkan bab ini jika Anda baru mulai mempelajari JavaScript.
Mengingat konsep dasar prinsip keterjangkauan dari bab Pengumpulan Sampah, kita dapat mencatat bahwa mesin JavaScript dijamin menyimpan nilai dalam memori yang dapat diakses atau digunakan.
Misalnya:
// variabel pengguna mempunyai referensi kuat ke objek biarkan pengguna = { nama: "John" }; // mari kita timpa nilai variabel pengguna pengguna = nol; // referensi hilang dan objek akan terhapus dari memori
Atau kode serupa, namun sedikit lebih rumit dengan dua referensi kuat:
// variabel pengguna mempunyai referensi kuat ke objek biarkan pengguna = { nama: "John" }; // menyalin referensi kuat ke objek ke dalam variabel admin biarkan admin = pengguna; // mari kita timpa nilai variabel pengguna pengguna = nol; // objek masih dapat dijangkau melalui variabel admin
Objek { name: "John" }
hanya akan dihapus dari memori jika tidak ada referensi kuat ke objek tersebut (jika kita juga menimpa nilai variabel admin
).
Dalam JavaScript, ada konsep yang disebut WeakRef
, yang berperilaku sedikit berbeda dalam kasus ini.
Istilah: “Referensi kuat”, “Referensi lemah”
Referensi kuat – adalah referensi ke suatu objek atau nilai, yang mencegahnya dihapus oleh pengumpul sampah. Dengan demikian, menyimpan objek atau nilai dalam memori, yang ditunjuknya.
Artinya, objek atau nilai tersebut tetap ada di memori dan tidak dikumpulkan oleh pengumpul sampah selama ada referensi kuat yang aktif ke objek atau nilai tersebut.
Dalam JavaScript, referensi biasa ke objek adalah referensi kuat. Misalnya:
// variabel pengguna mempunyai referensi kuat ke objek ini biarkan pengguna = { nama: "John" };
Referensi lemah – adalah referensi ke suatu objek atau nilai, yang tidak mencegahnya dihapus oleh pengumpul sampah. Suatu objek atau nilai dapat dihapus oleh pengumpul sampah jika, satu-satunya referensi yang tersisa terhadap objek atau nilai tersebut adalah referensi yang lemah.
Catatan hati-hati
Sebelum kita mendalaminya, perlu diperhatikan bahwa penggunaan struktur yang dibahas dalam artikel ini secara benar memerlukan pemikiran yang sangat cermat, dan sebaiknya dihindari jika memungkinkan.
WeakRef
– adalah sebuah objek, yang berisi referensi lemah ke objek lain, yang disebut target
atau referent
.
Keunikan WeakRef
adalah ia tidak mencegah pengumpul sampah menghapus objek referensinya. Dengan kata lain, objek WeakRef
tidak menjaga objek referent
tetap hidup.
Sekarang mari kita ambil variabel user
sebagai "referensi" dan buat referensi lemah dari variabel tersebut ke variabel admin
. Untuk membuat referensi lemah, Anda perlu menggunakan konstruktor WeakRef
, meneruskan objek target (objek yang ingin Anda jadikan referensi lemah).
Dalam kasus kami — ini adalah variabel user
:
// variabel pengguna mempunyai referensi kuat ke objek biarkan pengguna = { nama: "John" }; // variabel admin menyimpan referensi lemah ke objek biarkan admin = new WeakRef(pengguna);
Diagram di bawah menggambarkan dua jenis referensi: referensi kuat menggunakan variabel user
dan referensi lemah menggunakan variabel admin
:
Kemudian, pada titik tertentu, kita berhenti menggunakan variabel user
– variabel tersebut akan ditimpa, keluar dari cakupan, dll., sambil mempertahankan instance WeakRef
di variabel admin
:
// mari kita timpa nilai variabel pengguna pengguna = nol;
Referensi yang lemah terhadap suatu objek tidak cukup untuk membuatnya tetap “hidup”. Ketika satu-satunya referensi yang tersisa ke objek referensi adalah referensi yang lemah, pengumpul sampah bebas menghancurkan objek ini dan menggunakan memorinya untuk hal lain.
Namun, hingga objek tersebut benar-benar dimusnahkan, referensi lemah dapat mengembalikannya, meskipun tidak ada lagi referensi kuat ke objek tersebut. Artinya, objek kita menjadi semacam “kucing Schrödinger” – kita tidak dapat mengetahui secara pasti apakah objek tersebut “hidup” atau “mati”:
Pada titik ini, untuk mendapatkan objek dari instance WeakRef
, kita akan menggunakan metode deref()
miliknya.
Metode deref()
mengembalikan objek referensi yang ditunjuk oleh WeakRef
, jika objek tersebut masih ada di memori. Jika objek telah dihapus oleh pengumpul sampah, maka metode deref()
akan mengembalikan undefined
:
biarkan ref = admin.deref(); jika (ref) { // objek masih dapat diakses: kita dapat melakukan manipulasi apa pun dengannya } kalau tidak { //objek telah dikumpulkan oleh pemulung }
WeakRef
biasanya digunakan untuk membuat cache atau array asosiatif yang menyimpan objek intensif sumber daya. Hal ini memungkinkan seseorang untuk menghindari mencegah objek-objek ini dikumpulkan oleh pengumpul sampah hanya berdasarkan keberadaannya dalam cache atau array asosiatif.
Salah satu contoh utamanya – adalah situasi ketika kita memiliki banyak objek gambar biner (misalnya, direpresentasikan sebagai ArrayBuffer
atau Blob
), dan kita ingin mengaitkan nama atau jalur dengan setiap gambar. Struktur data yang ada kurang sesuai untuk tujuan berikut:
Menggunakan Map
untuk membuat asosiasi antara nama dan gambar, atau sebaliknya, akan menyimpan objek gambar dalam memori karena mereka ada di Map
sebagai kunci atau nilai.
WeakMap
juga tidak memenuhi syarat untuk tujuan ini: karena objek yang direpresentasikan sebagai kunci WeakMap
menggunakan referensi yang lemah, dan tidak dilindungi dari penghapusan oleh pengumpul sampah.
Namun, dalam situasi ini, kita memerlukan struktur data yang menggunakan referensi lemah dalam nilainya.
Untuk tujuan ini, kita bisa menggunakan koleksi Map
, yang nilainya merupakan instance WeakRef
yang mengacu pada objek besar yang kita perlukan. Akibatnya, kita tidak akan menyimpan objek-objek besar dan tidak perlu ini dalam memori lebih lama dari yang seharusnya.
Jika tidak, ini adalah cara untuk mendapatkan objek gambar dari cache jika masih dapat dijangkau. Jika sudah terkumpul sampahnya, kita akan re-generate atau mendownload ulang lagi.
Dengan cara ini, lebih sedikit memori yang digunakan dalam beberapa situasi.
Di bawah ini cuplikan kode yang mendemonstrasikan teknik penggunaan WeakRef
.
Singkatnya, kita menggunakan Map
dengan kunci string dan objek WeakRef
sebagai nilainya. Jika objek WeakRef
belum dikumpulkan oleh pengumpul sampah, kami mendapatkannya dari cache. Jika tidak, kami mengunduhnya kembali dan menyimpannya di cache untuk kemungkinan digunakan kembali lebih lanjut:
fungsi ambilImg() { // fungsi abstrak untuk mendownload gambar... } fungsi lemahRefCache(fetchImg) { // (1) const imgCache = Peta baru(); // (2) kembali (imgNama) => { // (3) const cachedImg = imgCache.get(imgName); // (4) jika (cachedImg?.deref()) { // (5) kembalikan cachedImg?.deref(); } const newImg = ambilImg(imgNama); // (6) imgCache.set(imgName, WeakRef baru(newImg)); // (7) kembalikan Img baru; }; } const getCachedImg = lemahRefCache(fetchImg);
Mari selidiki detail apa yang terjadi di sini:
weakRefCache
– adalah fungsi tingkat tinggi yang menggunakan fungsi lain, fetchImg
, sebagai argumen. Dalam contoh ini, kita dapat mengabaikan penjelasan rinci tentang fungsi fetchImg
, karena ini bisa berupa logika apa pun untuk mengunduh gambar.
imgCache
– adalah cache gambar, yang menyimpan hasil cache dari fungsi fetchImg
, dalam bentuk kunci string (nama gambar) dan objek WeakRef
sebagai nilainya.
Mengembalikan fungsi anonim yang menggunakan nama gambar sebagai argumen. Argumen ini akan digunakan sebagai kunci untuk gambar yang di-cache.
Mencoba mendapatkan hasil cache dari cache, menggunakan kunci yang disediakan (nama gambar).
Jika cache berisi nilai untuk kunci yang ditentukan, dan objek WeakRef
belum dihapus oleh pengumpul sampah, kembalikan hasil cache.
Jika tidak ada entri dalam cache dengan kunci yang diminta, atau metode deref()
mengembalikan undefined
(artinya objek WeakRef
telah dikumpulkan sampah), fungsi fetchImg
akan mengunduh gambar lagi.
Masukkan gambar yang diunduh ke dalam cache sebagai objek WeakRef
.
Sekarang kita mempunyai koleksi Map
, dimana kuncinya – adalah nama gambar sebagai string, dan nilai – adalah objek WeakRef
yang berisi gambar itu sendiri.
Teknik ini membantu menghindari pengalokasian sejumlah besar memori untuk objek yang membutuhkan banyak sumber daya, yang tidak digunakan lagi oleh siapa pun. Ini juga menghemat memori dan waktu jika menggunakan kembali objek yang di-cache.
Berikut adalah representasi visual dari tampilan kode ini:
Namun, implementasi ini mempunyai kelemahan: seiring berjalannya waktu, Map
akan diisi dengan string sebagai kunci, yang mengarah ke WeakRef
, yang objek rujukannya telah dikumpulkan sampahnya:
Salah satu cara untuk menangani masalah ini – adalah dengan mencari cache secara berkala dan membersihkan entri yang “mati”. Cara lain – adalah dengan menggunakan finalizer, yang akan kita bahas selanjutnya.
Kasus penggunaan lain untuk WeakRef
– adalah melacak objek DOM.
Bayangkan sebuah skenario di mana beberapa kode atau pustaka pihak ketiga berinteraksi dengan elemen di halaman kita selama elemen tersebut ada di DOM. Misalnya, ini bisa berupa utilitas eksternal untuk memantau dan memberi tahu tentang status sistem (biasanya disebut “logger” – program yang mengirimkan pesan informasi yang disebut “log”).
Contoh interaktif:
Hasil
indeks.js
indeks.css
indeks.html
const startMessagesBtn = document.querySelector('.start-messages'); // (1) const closeWindowBtn = document.querySelector('.window__button'); // (2) const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) startMessagesBtn.addEventListener('klik', () => { // (4) startMessages(windowElementRef); startMessagesBtn.disabled = benar; }); closeWindowBtn.addEventListener('klik', () => document.querySelector(".window__body").remove()); // (5) const startMessages = (elemen) => { const timerId = setInterval(() => { // (6) if (elemen.deref()) {// (7) const payload = dokumen.createElement("p"); payload.textContent = `Pesan: Status sistem OK: ${new Date().toLocaleTimeString()}`; elemen.deref().append(payload); } lain { // (8) alert("Elemen telah dihapus."); // (9) clearInterval(timerId); } }, 1000); };
.aplikasi { tampilan: fleksibel; arah fleksibel: kolom; celah: 16 piksel; } .mulai-pesan { lebar: konten sesuai; } .jendela { lebar: 100%; batas: 2 piksel padat #464154; meluap: tersembunyi; } .jendela__header { posisi: lengket; bantalan: 8 piksel; tampilan: fleksibel; justify-content: spasi-antara; menyelaraskan-item: tengah; warna latar belakang: #736e7e; } .jendela__judul { margin: 0; ukuran font: 24px; berat font: 700; warna: putih; spasi huruf: 1px; } .jendela__tombol { bantalan: 4 piksel; latar belakang: #4f495c; garis besar: tidak ada; batas: 2 piksel padat #464154; warna: putih; ukuran font: 16px; kursor: penunjuk; } .jendela__tubuh { tinggi: 250 piksel; bantalan: 16 piksel; meluap: gulir; warna latar belakang: #736e7e33; }
<!DOCTYPE HTML> <htmllang="id"> <kepala> <meta charset="utf-8"> <link rel="stylesheet" href="index.css"> <title>Pencatat DOM WeakRef</title> </kepala> <tubuh> <div class="aplikasi"> <button class="start-messages">Mulai mengirim pesan</button> <div kelas="jendela"> <div kelas="jendela__header"> <p class="window__title">Pesan:</p> <button class="window__button">Tutup</button> </div> <div kelas="jendela__tubuh"> Tidak ada pesan. </div> </div> </div> <skrip type="modul" src="index.js"></script> </tubuh> </html>
Ketika tombol “Mulai mengirim pesan” diklik, di apa yang disebut “jendela tampilan log” (sebuah elemen dengan kelas .window__body
), pesan (log) mulai muncul.
Namun, segera setelah elemen ini dihapus dari DOM, logger akan berhenti mengirim pesan. Untuk mereproduksi penghapusan elemen ini, cukup klik tombol “Tutup” di pojok kanan atas.
Agar tidak mempersulit pekerjaan kita, dan tidak memberi tahu kode pihak ketiga setiap kali elemen DOM kita tersedia, dan jika tidak, cukup membuat referensi yang lemah menggunakan WeakRef
.
Setelah elemen dihapus dari DOM, logger akan menyadarinya dan berhenti mengirim pesan.
Sekarang mari kita lihat lebih dekat kode sumbernya ( tab index.js
):
Dapatkan elemen DOM dari tombol “Mulai mengirim pesan”.
Dapatkan elemen DOM dari tombol "Tutup".
Dapatkan elemen DOM dari jendela tampilan log menggunakan konstruktor new WeakRef()
. Dengan cara ini, variabel windowElementRef
menyimpan referensi lemah ke elemen DOM.
Tambahkan pendengar acara pada tombol “Mulai mengirim pesan”, yang bertanggung jawab untuk memulai logger ketika diklik.
Tambahkan pendengar acara pada tombol “Tutup”, yang bertanggung jawab untuk menutup jendela tampilan log saat diklik.
Gunakan setInterval
untuk mulai menampilkan pesan baru setiap detik.
Jika elemen DOM pada jendela tampilan log masih dapat diakses dan disimpan di memori, buat dan kirim pesan baru.
Jika metode deref()
mengembalikan undefined
, itu berarti elemen DOM telah dihapus dari memori. Dalam hal ini, logger berhenti menampilkan pesan dan menghapus pengatur waktu.
alert
, yang akan dipanggil, setelah elemen DOM dari jendela tampilan log dihapus dari memori (yaitu setelah mengklik tombol “Tutup”). Perlu diperhatikan bahwa penghapusan dari memori mungkin tidak terjadi secara instan, karena hanya bergantung pada mekanisme internal pengumpul sampah.
Kami tidak dapat mengontrol proses ini langsung dari kode. Namun, meskipun demikian, kami masih memiliki opsi untuk memaksa pengumpulan sampah dari browser.
Di Google Chrome, misalnya, untuk melakukan ini, Anda perlu membuka alat pengembang ( Ctrl + Shift + J di Windows/Linux atau Option + ⌘ + J di macOS), buka tab “Performa”, dan klik pada tombol ikon tempat sampah – “Kumpulkan sampah”:
Fungsionalitas ini didukung di sebagian besar browser modern. Setelah tindakan diambil, alert
akan segera terpicu.
Sekarang saatnya berbicara tentang finalisator. Sebelum kita melanjutkan, mari kita perjelas terminologinya:
Panggilan balik pembersihan (finalizer) – adalah fungsi yang dijalankan, ketika sebuah objek, yang terdaftar di FinalizationRegistry
, dihapus dari memori oleh pengumpul sampah.
Tujuannya – adalah untuk memberikan kemampuan untuk melakukan operasi tambahan, terkait dengan objek, setelah akhirnya dihapus dari memori.
Registry (atau FinalizationRegistry
) – adalah objek khusus dalam JavaScript yang mengelola registrasi dan pembatalan registrasi objek serta callback pembersihannya.
Mekanisme ini memungkinkan pendaftaran objek untuk melacak dan mengaitkan panggilan balik pembersihan dengannya. Pada dasarnya ini adalah struktur yang menyimpan informasi tentang objek terdaftar dan callback pembersihannya, dan kemudian secara otomatis memanggil callback tersebut ketika objek dihapus dari memori.
Untuk membuat instance FinalizationRegistry
, ia perlu memanggil konstruktornya, yang mengambil satu argumen – panggilan balik pembersihan (finalizer).
Sintaksis:
fungsi pembersihanCallback(heldValue) { // membersihkan kode panggilan balik } const registry = FinalizationRegistry baru (cleanupCallback);
Di Sini:
cleanupCallback
– panggilan balik pembersihan yang akan dipanggil secara otomatis ketika objek terdaftar dihapus dari memori.
heldValue
– nilai yang diteruskan sebagai argumen ke panggilan balik pembersihan. Jika heldValue
adalah sebuah objek, registri menyimpan referensi yang kuat ke objek tersebut.
registry
– sebuah instance dari FinalizationRegistry
.
Metode FinalizationRegistry
:
register(target, heldValue [, unregisterToken])
– digunakan untuk mendaftarkan objek di registri.
target
– objek yang didaftarkan untuk pelacakan. Jika target
adalah sampah yang dikumpulkan, callback pembersihan akan dipanggil dengan heldValue
sebagai argumennya.
unregisterToken
opsional – token pembatalan pendaftaran. Hal ini dapat diteruskan untuk membatalkan pendaftaran suatu objek sebelum pengumpul sampah menghapusnya. Biasanya, objek target
digunakan sebagai unregisterToken
, yang merupakan praktik standar.
unregister(unregisterToken)
– metode unregister
digunakan untuk membatalkan pendaftaran objek dari registri. Dibutuhkan satu argumen – unregisterToken
(token unregister yang diperoleh saat mendaftarkan objek).
Sekarang mari kita beralih ke contoh sederhana. Mari gunakan objek user
yang sudah dikenal dan buat instance FinalizationRegistry
:
biarkan pengguna = { nama: "John" }; const registry = new FinalizationRegistry((heldValue) => { console.log(`${heldValue} telah dikumpulkan oleh pemulung.`); });
Kemudian, kita akan mendaftarkan objek tersebut, yang memerlukan panggilan balik pembersihan dengan memanggil metode register
:
registry.register(pengguna, nama pengguna);
Registri tidak menyimpan referensi yang kuat ke objek yang didaftarkan, karena hal ini akan menggagalkan tujuannya. Jika registri menyimpan referensi yang kuat, maka objek tersebut tidak akan pernah menjadi sampah yang dikumpulkan.
Jika objek dihapus oleh pengumpul sampah, callback pembersihan kita mungkin akan dipanggil suatu saat nanti, dengan heldValue
diteruskan ke objek tersebut:
// Ketika objek pengguna dihapus oleh pengumpul sampah, pesan berikut akan dicetak di konsol: "John telah dikumpulkan oleh pemulung."
Ada juga situasi di mana, bahkan dalam implementasi yang menggunakan panggilan balik pembersihan, ada kemungkinan panggilan tersebut tidak akan dipanggil.
Misalnya:
Saat program menghentikan operasinya sepenuhnya (misalnya, saat menutup tab di browser).
Ketika instance FinalizationRegistry
itu sendiri tidak lagi dapat dijangkau oleh kode JavaScript. Jika objek yang membuat instance FinalizationRegistry
keluar dari cakupan atau dihapus, callback pembersihan yang terdaftar di registri tersebut mungkin juga tidak dipanggil.
Kembali ke contoh cache yang lemah , kita dapat melihat hal berikut:
Meskipun nilai-nilai yang dibungkus dalam WeakRef
telah dikumpulkan oleh pengumpul sampah, namun masih terdapat masalah “kebocoran memori” berupa sisa kunci yang nilainya telah dikumpulkan oleh pengumpul sampah.
Berikut adalah contoh caching yang ditingkatkan menggunakan FinalizationRegistry
:
fungsi ambilImg() { // fungsi abstrak untuk mendownload gambar... } fungsi lemahRefCache(fetchImg) { const imgCache = Peta baru(); const registry = new FinalizationRegistry((imgName) => { // (1) const cachedImg = imgCache.get(imgName); if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); }); kembali (imgNama) => { const cachedImg = imgCache.get(imgName); jika (cachedImg?.deref()) { kembalikan cachedImg?.deref(); } const newImg = ambilImg(imgNama); imgCache.set(imgName, WeakRef baru(newImg)); registry.register(newImg, imgName); // (2) kembalikan Img baru; }; } const getCachedImg = lemahRefCache(fetchImg);
Untuk mengelola pembersihan entri cache "mati", ketika objek WeakRef
terkait dikumpulkan oleh pengumpul sampah, kami membuat registri pembersihan FinalizationRegistry
.
Poin penting di sini adalah, bahwa dalam panggilan balik pembersihan, harus diperiksa, apakah entri tersebut dihapus oleh pengumpul sampah dan tidak ditambahkan kembali, agar tidak menghapus entri "langsung".
Setelah nilai (gambar) baru diunduh dan dimasukkan ke dalam cache, kami mendaftarkannya di registri finalizer untuk melacak objek WeakRef
.
Implementasi ini hanya berisi pasangan kunci/nilai aktual atau “langsung”. Dalam hal ini, setiap objek WeakRef
terdaftar di FinalizationRegistry
. Dan setelah objek dibersihkan oleh pengumpul sampah, panggilan balik pembersihan akan menghapus semua nilai undefined
.
Berikut adalah representasi visual dari kode yang diperbarui:
Aspek kunci dari implementasi yang diperbarui adalah finalizer memungkinkan pembuatan proses paralel antara program “utama” dan callback pembersihan. Dalam konteks JavaScript, program “utama” – adalah kode JavaScript kami, yang dijalankan dan dijalankan di aplikasi atau halaman web kami.
Oleh karena itu, dari saat suatu objek ditandai untuk dihapus oleh pengumpul sampah, dan hingga pelaksanaan panggilan balik pembersihan yang sebenarnya, mungkin terdapat jeda waktu tertentu. Penting untuk dipahami bahwa selama jeda waktu ini, program utama dapat membuat perubahan apa pun pada objek atau bahkan mengembalikannya ke memori.
Itu sebabnya, dalam panggilan balik pembersihan, kita harus memeriksa apakah ada entri yang ditambahkan kembali ke cache oleh program utama untuk menghindari penghapusan entri "langsung". Demikian pula, saat mencari kunci di cache, ada kemungkinan nilai tersebut telah dihapus oleh pengumpul sampah, namun panggilan balik pembersihan belum dijalankan.
Situasi seperti ini memerlukan perhatian khusus jika Anda bekerja dengan FinalizationRegistry
.
Beralih dari teori ke praktik, bayangkan skenario kehidupan nyata, saat pengguna menyinkronkan foto mereka di perangkat seluler dengan beberapa layanan cloud (seperti iCloud atau Google Foto), dan ingin melihatnya dari perangkat lain. Selain fungsi dasar melihat foto, layanan tersebut menawarkan banyak fitur tambahan, misalnya:
Pengeditan foto dan efek video.
Menciptakan “kenangan” dan album.
Montase video dari serangkaian foto.
…dan masih banyak lagi.
Di sini, sebagai contoh, kita akan menggunakan implementasi layanan semacam itu yang cukup primitif. Poin utamanya – adalah untuk menunjukkan kemungkinan skenario penggunaan WeakRef
dan FinalizationRegistry
secara bersamaan dalam kehidupan nyata.
Berikut tampilannya:
Di sisi kiri, ada perpustakaan foto cloud (ditampilkan sebagai thumbnail). Kita dapat memilih gambar yang kita perlukan dan membuat kolase, dengan mengklik tombol "Buat kolase" di sisi kanan halaman. Kemudian, kolase yang dihasilkan dapat diunduh sebagai gambar.
Untuk meningkatkan kecepatan pemuatan halaman, sebaiknya unduh dan tampilkan thumbnail foto dalam kualitas terkompresi . Namun, untuk membuat kolase dari foto yang dipilih, unduh dan gunakan dalam kualitas ukuran penuh .
Di bawah ini kita dapat melihat bahwa ukuran intrinsik thumbnail adalah 240x240 piksel. Ukuran tersebut dipilih dengan tujuan untuk meningkatkan kecepatan pemuatan. Selain itu, kita tidak memerlukan foto ukuran penuh dalam mode pratinjau.
Mari kita asumsikan, kita perlu membuat kolase dari 4 foto: kita memilihnya, lalu klik tombol "Buat kolase". Pada tahap ini, fungsi weakRefCache
yang sudah kita kenal memeriksa apakah gambar yang diperlukan ada dalam cache. Jika tidak, ia mengunduhnya dari cloud dan menyimpannya di cache untuk digunakan lebih lanjut. Hal ini terjadi untuk setiap gambar yang dipilih:
Dengan memperhatikan output di konsol, Anda dapat melihat foto mana yang diunduh dari cloud – ini ditandai dengan FETCHED_IMAGE . Karena ini adalah upaya pertama untuk membuat kolase, ini berarti, pada tahap ini, “cache lemah” masih kosong, dan semua foto diunduh dari cloud dan dimasukkan ke dalamnya.
Namun selain proses download gambar, ada juga proses pembersihan memori yang dilakukan oleh pengumpul sampah. Artinya, objek yang disimpan dalam cache yang kita rujuk menggunakan referensi yang lemah akan dihapus oleh pengumpul sampah. Dan finalizer kami berhasil dijalankan, sehingga menghapus kunci yang digunakan untuk menyimpan gambar dalam cache. CLEANED_IMAGE memberi tahu kami tentang hal ini:
Selanjutnya, kami menyadari bahwa kami tidak menyukai kolase yang dihasilkan, dan memutuskan untuk mengubah salah satu gambar dan membuat yang baru. Untuk melakukan ini, cukup batalkan pilihan gambar yang tidak diperlukan, pilih gambar lain, dan klik lagi tombol "Buat kolase":
Namun kali ini tidak semua gambar diunduh dari jaringan, dan salah satunya diambil dari cache yang lemah: pesan CACHED_IMAGE memberi tahu kita tentang hal ini. Artinya, pada saat pembuatan kolase, pengumpul sampah belum menghapus gambar kami, dan kami dengan berani mengambilnya dari cache, sehingga mengurangi jumlah permintaan jaringan dan mempercepat keseluruhan waktu proses pembuatan kolase:
Mari kita "bermain-main" lagi, dengan mengganti salah satu gambar lagi dan membuat kolase baru:
Kali ini hasilnya lebih mengesankan. Dari 4 gambar yang dipilih, 3 diantaranya diambil dari cache yang lemah, dan hanya satu yang harus diunduh dari jaringan. Pengurangan beban jaringan sekitar 75%. Mengesankan, bukan?
Tentu saja, penting untuk diingat, bahwa perilaku seperti itu tidak dijamin, dan bergantung pada penerapan dan pengoperasian khusus pengumpul sampah.
Berdasarkan hal ini, pertanyaan logis segera muncul: mengapa kita tidak menggunakan cache biasa, di mana kita dapat mengelola sendiri entitasnya, daripada mengandalkan pengumpul sampah? Benar, dalam sebagian besar kasus, WeakRef
dan FinalizationRegistry
tidak diperlukan.
Di sini, kami hanya mendemonstrasikan implementasi alternatif dari fungsi serupa, menggunakan pendekatan non-sepele dengan fitur bahasa yang menarik. Namun, kita tidak bisa mengandalkan contoh ini, jika kita memerlukan hasil yang konstan dan dapat diprediksi.
Anda dapat membuka contoh ini di kotak pasir.
WeakRef
– dirancang untuk membuat referensi lemah ke objek, memungkinkan objek tersebut dihapus dari memori oleh pengumpul sampah jika tidak ada lagi referensi kuat ke objek tersebut. Hal ini bermanfaat untuk mengatasi penggunaan memori yang berlebihan dan mengoptimalkan pemanfaatan sumber daya sistem pada aplikasi.
FinalizationRegistry
– adalah alat untuk mendaftarkan callback, yang dijalankan ketika objek yang tidak lagi direferensikan secara kuat, dimusnahkan. Hal ini memungkinkan pelepasan sumber daya yang terkait dengan objek atau melakukan operasi lain yang diperlukan sebelum menghapus objek dari memori.