WeakMap va WeakSet — nima uchun kerak
Muammodan boshlaylik
DOM elementlarga qo'shimcha ma'lumot bog'lashingiz kerak. Har bir <button> ga necha marta bosilganini saqlash.
Oddiy Map bilan:
const bosishSoni = new Map();
document.querySelectorAll("button").forEach(tugma => {
tugma.addEventListener("click", function() {
const hozirgi = bosishSoni.get(this) || 0;
bosishSoni.set(this, hozirgi + 1);
});
});
Ishlaydi. Lekin muammo bor.
Foydalanuvchi sahifaning bir qismini yangiladi — eski tugmalar DOM dan o'chirildi, yangilari qo'shildi. Eski tugmalar xotirada yo'q — lekin bosishSoni Map da ular hali saqlanmoqda. Map tugmaga reference ushlab turibdi — garbage collector o'chirolmaydi.
Sahifa uzoq ishlasa — o'chirilgan elementlar to'planib, xotira oqishi (memory leak) bo'ladi.
WeakMap — bu muammoni hal qiladi.
Garbage Collector va "kuchli" reference
JavaScript xotirani avtomatik boshqaradi. Ob'ektga hech qanday reference qolmasa — garbage collector uni o'chiradi, xotirani bo'shatadi.
"Kuchli" reference — oddiy o'zgaruvchi, Map kalit, massiv elementi — ular ob'ektni "ushlab turadi". Garbage collector bu ob'ektga tegmaydi.
Hayotiy o'xshatma
Mehmonxona va ro'yxat. Mehmon xonaga joylashdi — ro'yxatga yozildi. Mehmon jo'nab ketdi, lekin ro'yxatdan o'chirilmadi. Mehmonxona "bu xona band" deb hisoblab turadi — yangi mehmon kira olmaydi. Xona bo'sh — lekin "band". Bu — memory leak o'xshatmasi.
Garbage collector = mehmonxona xodimi. U faqat ro'yxatda yo'q mehmonlarning xonasini bo'shatadi. Agar Map da reference bo'lsa — "ro'yxatda bor" demak.
WeakMap = ro'yxatdan o'z-o'zidan o'chishi. Mehmon jo'nashi bilanoq — xona avtomatik bo'shatiladi. Alohida o'chirish kerak emas.
WeakMap — "kuchsiz" reference
WeakMap — oddiy Map ga o'xshash, lekin kalitlar faqat ob'ekt bo'lishi mumkin. Va bu reference — "kuchsiz" (weak). Ob'ektga boshqa reference qolmasa — WeakMap dagi yozuv avtomatik o'chadi.
const bosishSoni = new WeakMap();
document.querySelectorAll("button").forEach(tugma => {
tugma.addEventListener("click", function() {
const hozirgi = bosishSoni.get(this) || 0;
bosishSoni.set(this, hozirgi + 1);
});
});
Endi tugma DOM dan o'chirilganda — unga boshqa reference qolmaydi. WeakMap dagi yozuv ham garbage collector tomonidan tozalanadi. Memory leak yo'q.
WeakMap metodlari
const wm = new WeakMap();
const ob = { ism: "Jasur" };
wm.set(ob, "qo'shimcha ma'lumot");
wm.get(ob); // "qo'shimcha ma'lumot"
wm.has(ob); // true
wm.delete(ob); // o'chirish
// ❌ — WeakMap iterate qilib bo'lmaydi
wm.forEach(...); // yo'q bunday metod
wm.size; // yo'q — uzunlik bilish mumkin emas
[...wm]; // ❌ — iterable emas
Nima uchun iterate qilib bo'lmaydi? Chunki garbage collector istalgan vaqt ob'ektni o'chirishi mumkin. Agar ro'yxat olish mumkin bo'lsa — siz bir ro'yxat olasiz, bir soniya o'tib boshqa ro'yxat — ular farq qiladi. Bu — ishonchsiz holat. Shuning uchun WeakMap da forEach, keys, size yo'q.
WeakMap — private ma'lumotlar uchun
# private field lardan oldin WeakMap private ma'lumot saqlash uchun ishlatilardi:
const _balans = new WeakMap(); // ← tashqarida, lekin faqat bu modul ko'radi
class BankHisobi {
constructor(boshlangich) {
_balans.set(this, boshlangich); // ← this — kalit, balans — qiymat
}
yechish(miqdor) {
const hozirgi = _balans.get(this);
if (miqdor > hozirgi) throw new Error("Yetarli emas");
_balans.set(this, hozirgi - miqdor);
}
get balans() {
return _balans.get(this);
}
}
const hisob = new BankHisobi(1_000_000);
console.log(hisob.balans); // 1_000_000
hisob.yechish(200_000);
console.log(hisob.balans); // 800_000
// Tashqaridan ko'rib bo'lmaydi:
console.log(hisob._balans); // undefined — WeakMap da emas
_balans.get({}); // undefined — hisob ob'ekti emas
hisob ob'ekti yo'qolganda — _balans dagi yozuv ham avtomatik o'chadi.
WeakSet — ob'ektlar to'plami
WeakSet — Set ga o'xshash, faqat ob'ektlar uchun, "kuchsiz" reference bilan.
const ko_rilganlar = new WeakSet();
function birMartalikKo'rsat(element) {
if (ko_rilganlar.has(element)) {
return; // ← allaqachon ko'rsatilgan
}
ko_rilganlar.add(element);
element.style.opacity = "1"; // ← ko'rsatish
}
Element DOM dan o'chirilganda — ko_rilganlar dan ham avtomatik ketadi.
WeakSet metodlari
const ws = new WeakSet();
const ob = { id: 1 };
ws.add(ob);
ws.has(ob); // true
ws.delete(ob);
// WeakMap kabi — iterate qilib bo'lmaydi
ws.size; // yo'q
ws.forEach; // yo'q
Map va WeakMap — qachon qaysi?
| Map | WeakMap | |
|---|---|---|
| Kalit turi | istalgan (string, number, ob'ekt) | faqat ob'ekt |
| Garbage collection | kalit ushlab turadi | kalit ketsa — yozuv ham ketadi |
| Iterate qilish | forEach, keys, size |
yo'q |
| Qachon | kalit string/number bo'lsa yoki iterate kerak | ob'ektga ma'lumot bog'lash, memory leak xavfi bo'lsa |
Map ishlatish:
- Kalitlar — string, number, Symbol
- Barcha elementlarni ko'rib chiqish kerak
- Uzunlik bilish kerak
WeakMap ishlatish:
- Ob'ektlarga qo'shimcha ma'lumot bog'lash (DOM elementlar, ob'ektlar)
- Ob'ekt yo'qolganda ma'lumot ham ketishi kerak
- Memory leak xavfi bo'lganda
Tez-tez uchraydigan xatolar
Primitive qiymatni WeakMap kalit sifatida:
const wm = new WeakMap();
wm.set("matn", "qiymat"); // ❌ TypeError: Invalid value used as weak map key
wm.set(42, "qiymat"); // ❌ TypeError
wm.set(null, "qiymat"); // ❌ TypeError
const ob = {};
wm.set(ob, "qiymat"); // ✅ — faqat ob'ekt
WeakMap da iterate qilishga urinish:
const wm = new WeakMap();
for (const [kalit, qiymat] of wm) { // ❌ TypeError: wm is not iterable
}
wm.forEach(...); // ❌ TypeError: wm.forEach is not a function
WeakMap iterate qilib bo'lmaydi — bu dizayn qarori, cheklov emas.
Xulosa
WeakMap — ob'ektga "kuchsiz" reference bilan ma'lumot bog'lash. Ob'ekt yo'qolganda — ma'lumot ham avtomatik ketadi. Memory leak yo'q.
WeakSet — ob'ektlar to'plami, "kuchsiz" reference bilan.
Ikkalasi iterate qilib bo'lmaydi — garbage collector istalgan vaqt tozalashi mumkin.
Asosiy ishlatilishi: DOM elementlar yoki ob'ektlarga qo'shimcha ma'lumot bog'lash, ob'ekt yo'qolganda ma'lumot ham yo'qolishi kerak bo'lganda.
Keyingi dars
62-dars: Map va Set — Object va Array dan farqi, qachon ishlatiladi