WeakMap va WeakSet — nima uchun kerak

~15 daq · JavaScript · Ilg'or mavzular

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