IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur47/65-dars19 daqiqa
Mundarija (31)

JavaScript WeakMap, WeakSet, WeakRef va FinalizationRegistry: kuchsiz havolalar

Qisqacha: Oddiy Map kalit obyektni xotirada ushlab turadi — u boshqa hech qayerda kerak bo'lmasa ham. WeakMap va WeakSet esa obyektni "kuchsiz" ushlaydi: obyektga boshqa havola qolmasa, axlat yig'uvchi uni yozuvi bilan birga o'chiradi. Shuning uchun ular begona obyektga qo'shimcha ma'lumot (metadata) va kesh uchun qulay. WeakRef va FinalizationRegistry — kamdan-kam kerak bo'ladigan, xulqi oldindan aytib bo'lmaydigan vositalar.

Bu darsda

  • Kuchli va kuchsiz havola farqini va axlat yig'uvchi obyektni qachon o'chirishini tushuntira olasiz.
  • WeakMap bilan obyektga uning o'zini o'zgartirmasdan ma'lumot biriktira olasiz.
  • WeakSet bilan "bu obyekt ko'rildimi?" degan belgini qo'ya olasiz.
  • WeakRef va FinalizationRegistry nima qilishini va nega ularga tayanib bo'lmasligini bilasiz.
  • Map va WeakMap xotira farqini o'zingiz o'lchay olasiz.

Oldin bilishingiz kerak: Map, Set, Closure tuzoqlari, Havola semantikasi, Private maydon va metodlar.

1. Nega bu kerak?

Closure tuzoqlari darsida axlat yig'ish (garbage collection, GC) bilan tanishgan edik. JavaScript xotirani o'zi tozalaydi: hech qayerdan yetib bo'lmaydigan obyekt o'chiriladi. U yerda "closure eslagan narsa tozalanmaydi" degan edik. Map ham xuddi shunday "eslaydi".

Tasavvur qiling: «Bahor» sayti har mehmon uchun obyekt yaratadi. Mehmonga oid qo'shimcha ma'lumotni — masalan, sahifada necha marta bosganini — Map da saqlaymiz:

js
const bosishlar = new Map();
// mehmon obyekti — kalit, bosishlar soni — qiymat
bosishlar.set(mehmon, 3);

Mehmon saytdan chiqdi, dastur mehmon obyektini boshqa ishlatmaydi. Lekin bosishlar hali ham uni kalit sifatida ushlab turibdi. Axlat yig'uvchi qaraydi: "bu obyektga Map dan yo'l bor — demak kerak". Obyekt ham, uning yozuvi ham xotirada qoladi. Kun davomida minglab mehmon — minglab "unutilgan", lekin o'chmaydigan obyekt. Bu — Closure tuzoqlari darsida aytilgan xotira sizib chiqishi.

Bu muammoni hal qilish uchun maxsus to'plamlar bor — kuchsiz to'plamlar. Hayotdan o'xshatish: kiyim saqlash xonasi. Map — paltoni abadiy saqlaydigan xona. Egasi jetonni yo'qotsa ham, palto osilib turaveradi. WeakMap esa aqlliroq xona. Jetonning egasi yo'qolsa, palto ham chiqarib yuboriladi. Garderob to'lib ketmaydi.

2. Axlat yig'uvchi qanday qaror qiladi

2.1 Yetib borish mumkinmi?

Axlat yig'uvchi bitta savol beradi: "obyektga ildizdan yetib borsa bo'ladimi?" Ildiz (root) — har doim tirik joylar: global o'zgaruvchilar va hozir bajarilayotgan funksiyalarning o'zgaruvchilari (Execution context va call stack). Daraxt o'xshatishi: ildizga shox orqali ulangan barg tirik, uzilib tushgan barg esa supurib tashlanadi. Ildizdan havolalar bo'ylab yurib yetib bo'ladigan obyekt — tirik. Yetib bo'lmaydigani — axlat.

Kuchli havola (strong reference) — obyektni tirik ushlab turadigan havola: o'zgaruvchi, massiv elementi, obyekt xususiyati, Map kaliti yoki qiymati. Kuchsiz havola (weak reference) — obyektni ko'rsatadi, lekin uni tirik ushlab turmaydi. Axlat yig'uvchi kuchsiz havolalarni hisobga olmaydi.

Sxemada uzluksiz strelka — kuchli havola, punktir — kuchsiz:

flowchart LR
  I(["Ildiz:<br/>o'zgaruvchilar"]) --> R["mijozlar massivi"]
  R --> A["mijoz A"]
  I --> M["Map"]
  M --> B["mijoz B"]
  I --> W["WeakMap"]
  W -.-> C["mijoz C"]

Uchala mijozga ham biror strelka boradi. Lekin natija har xil:

  • A — massiv orqali yetib boriladi. Tirik.
  • B — massivda yo'q, lekin Map uni kuchli ushlaydi. Tirik — garchi dasturga kerak bo'lmasa ham.
  • C — faqat WeakMap ning punktir strelkasi. Axlat yig'uvchi uchun bu "hech kim". C o'chiriladi va WeakMap dagi yozuvi ham yo'qoladi.

2.2 Qachon o'chiriladi — noma'lum

Muhim halol gap: axlat yig'uvchi qachon ishlashini siz bilmaysiz va boshqara olmaysiz. JavaScript dvigateli (Chrome va Node'da — V8) uni o'zi hal qiladi: xotira tanqis bo'lganda, dastur bo'sh turganda, yoki umuman ishga tushirmasligi ham mumkin. Standart faqat "yetib bo'lmaydigan obyekt o'chirilishi mumkin" deydi, "o'chiriladi" demaydi.

Shuning uchun bu darsdagi qoida: kuchsiz to'plamlar xotirani tejaydi, lekin dastur mantiqi obyekt qachon o'chishiga tayanmasin.

Tekshirib ko'ring: let a = { ism: "Ali" }; const b = a; a = null; — obyekt axlatmi?

Javob

Yo'q. a endi null, lekin b hali ham o'sha obyektga kuchli havola. Ildizdan b orqali yetib boriladi — obyekt tirik. Havolalarning hammasi yo'qolgandagina u axlatga aylanadi.

3. WeakMap — obyektga ma'lumot biriktirish

3.1 Asosiy ishlatish

WeakMap — kalitlari kuchsiz ushlanadigan Map. Metodlari tanish:

js
const bosishlar = new WeakMap();
const mehmon = { ism: "Bobur" };

bosishlar.set(mehmon, 1);
bosishlar.set(mehmon, bosishlar.get(mehmon) + 1);

console.log(bosishlar.get(mehmon)); // 2
console.log(bosishlar.has(mehmon)); // true
console.log(bosishlar.delete(mehmon)); // true
console.log(bosishlar.has(mehmon)); // false

set, get, has, delete — Map dagidek. Lekin uchta muhim farq bor.

3.2 Kalit faqat obyekt

js
const narxlar = new WeakMap();
narxlar.set("osh", 35000);
text
TypeError: Invalid value used as weak map key

Tarjimasi: "Kuchsiz map kaliti sifatida yaroqsiz qiymat ishlatildi". Nega? Satr, son kabi primitivni "o'chirib" bo'lmaydi — "osh" satrini kodning istalgan joyida qayta yozish mumkin, u hech qachon "yetib bo'lmaydigan" bo'lmaydi. Kuchsiz havola faqat yo'qolishi mumkin bo'lgan narsa uchun ma'noli: obyekt (massiv, funksiya, DOM elementi ham obyekt). Yana bitta istisno bor — ro'yxatdan o'tmagan symbol; uni keyingi darsda, Symbol da ko'ramiz.

3.3 Aylanib bo'lmaydi, size yo'q

js
const bosishlar = new WeakMap();
bosishlar.set({ ism: "Bobur" }, 1);

console.log(bosishlar); // WeakMap { <items unknown> }
console.log(bosishlar.size); // undefined
console.log(typeof bosishlar.keys); // undefined

WeakMap da size, keys, values, forEach va clear yo'q. for...of ham ishlamaydi. Node uni <items unknown> — "elementlar noma'lum" deb ko'rsatadi.

Nega bunday cheklov? Ichidagi yozuvlar axlat yig'uvchi ishlagan paytga qarab yo'qolishi mumkin. Agar size bo'lganda, u bir xil kodda bir safar 3, keyingi safar 1 qaytarishi mumkin edi. Natija GC ga bog'liq bo'lib qolardi. Standart buni ataylab yopgan: WeakMap dan faqat kalitingiz bor obyekt haqida so'rash mumkin. Kalit qo'lingizda bo'lsa — obyekt tirik, javob aniq.

3.4 Asosiy foydasi: obyektni o'zgartirmasdan ma'lumot qo'shish

Nega bosishlar ni mehmon.bosishlar = 2 deb obyektning o'ziga yozmaymiz? Uch sabab bor:

  • Obyekt sizniki emas. U kutubxonadan, serverdan yoki DOM'dan kelgan. Unga xususiyat qo'shish — begona narsaga yozish: nom to'qnashishi, keyingi versiyada buzilish xavfi.
  • Obyekt muzlatilgan. Obyektni himoyalash darsidagi Object.freeze bilan qotirilgan obyektga yozib bo'lmaydi. WeakMap ga esa uni kalit qilish mumkin.
  • Ma'lumot tashqariga chiqmasin. Obyektga yozilgan xususiyat JSON.stringify da, Object.keys da, konsolda ko'rinadi. WeakMap dagi ma'lumot obyektning o'zida yo'q.
js
const menyu = Object.freeze({ nom: "Osh", narx: 35000 });
const korishlar = new WeakMap();

korishlar.set(menyu, 12);

console.log(korishlar.get(menyu)); // 12
console.log(JSON.stringify(menyu)); // {"nom":"Osh","narx":35000}

Muzlatilgan obyekt o'zgarmadi, JSON'da ortiqcha narsa yo'q — lekin ko'rishlar soni bor. Obyekt yo'qolsa, uning 12 raqami ham WeakMap dan o'zi ketadi.

3.5 DOM elementlari bilan

Brauzerda bu eng ko'p uchraydigan holat. Har tugmaga sozlama biriktiramiz:

js
const sozlamalar = new WeakMap();

for (const tugma of document.querySelectorAll("button")) {
  sozlamalar.set(tugma, { bosildi: 0 });
}

document.addEventListener("click", (event) => {
  const s = sozlamalar.get(event.target);
  if (s) {
    s.bosildi++;
  }
});

Tugma sahifadan o'chirilsa (tugma.remove()) va unga boshqa havola qolmasa, uning sozlamasi ham xotiradan ketadi. Map da esa o'chirilgan tugma va sozlamasi sahifa yopilguncha xotirada qolardi. Element sozlamasini saqlashning bu usuli kutubxonalarda keng tarqalgan.

3.6 Private ma'lumot — tarixiy usul

Private maydon va metodlar darsida # bilan haqiqiy private maydon yasadik. U 2022-yilda standartga kirgan. Undan oldin private ma'lumot WeakMap bilan yasalardi:

js
const yashirin = new WeakMap();

class Hamyon {
  constructor(summa) {
    yashirin.set(this, { summa });
  }

  qoldiq() {
    return yashirin.get(this).summa;
  }
}

const h = new Hamyon(50000);
console.log(h.qoldiq()); // 50000
console.log(Object.keys(h)); // []

summa nusxaning o'zida yo'q — u modul ichidagi yashirin da. Nusxa yo'qolsa, yozuv ham ketadi. Bugun buning o'rniga #summa yoziladi. Lekin eski kodda bu qolipni uchratasiz. Qolaversa, kodni eski brauzerlar tushunadigan ko'rinishga o'giradigan vositalar (TypeScript, Babel — kursda keyinroq tanishamiz) # maydonlarni aynan WeakMap ga aylantirib chiqaradi.

Tekshirib ko'ring: Nega WeakMap da size yo'q?

Javob

Yozuvlar axlat yig'uvchi ishlaganda, oldindan aytib bo'lmaydigan paytda yo'qoladi. size bo'lganda, bir xil kod har safar har xil javob berishi mumkin edi. Standart natija GC'ga bog'liq bo'lmasligi uchun size, aylanish va clear ni yopgan.

4. WeakSet — "ko'rildi" belgisi

WeakSet — obyektlarni kuchsiz saqlaydigan Set. Metodlari uchta: add, has, delete. Qiymat faqat obyekt bo'ladi, size va aylanish yo'q.

U bitta savolga javob beradi: "bu obyekt bilan allaqachon ishladikmi?". Klassik misol — aylanma havola. JSON chuqur darsida obyekt o'ziga ishora qilsa, JSON.stringify xato bergan edi. O'zimiz obyekt ichini rekursiya bilan aylansak, aylanma havolada cheksiz aylanib qolamiz. WeakSet bundan saqlaydi:

js
function sana(obyekt, korilgan = new WeakSet()) {
  if (korilgan.has(obyekt)) {
    return 0;
  }
  korilgan.add(obyekt);
  let soni = 1;
  for (const qiymat of Object.values(obyekt)) {
    if (typeof qiymat === "object" && qiymat !== null) {
      soni += sana(qiymat, korilgan);
    }
  }
  return soni;
}

const stol = { raqam: 3 };
const buyurtma = { taom: "osh", stol };
stol.oxirgiBuyurtma = buyurtma;

console.log(sana(buyurtma)); // 2

stol va buyurtma bir-biriga ishora qiladi. korilgan bo'lmasa, sana ular orasida abadiy aylanardi. WeakSet ikkinchi uchrashda "bu ko'rilgan" deydi. Funksiya tugagach, korilgan ham kerak bo'lmaydi. Lekin ichida aynan Set emas, WeakSet bo'lgani uchun u uzoq yashasa ham obyektlarni ushlab qolmaydi.

Boshqa misol — "bu formani bir marta yuborishga ruxsat": yuborilgan forma elementi WeakSet ga qo'shiladi, ikkinchi bosishda has tekshiriladi.

Tekshirib ko'ring: const ws = new WeakSet(); ws.add({ id: 1 }); — keyin ws.has({ id: 1 }) nima qaytaradi?

Javob

false. WeakSet ham Set kabi obyektlarni havola bo'yicha solishtiradi: has ichidagi { id: 1 } — boshqa, yangi obyekt. Qo'shilgan obyektning havolasi esa hech qayerda saqlanmadi — u endi hech kimga kerak emas va axlat yig'uvchi uni istalgan payt olib ketadi.

5. O'lchab ko'ramiz: Map va WeakMap

Gap yetarli — xotirani o'lchaymiz. Odatda dasturdan axlat yig'uvchini chaqirib bo'lmaydi. Lekin Node'da sinov uchun maxsus bayroq bor: --expose-gc. U global gc() funksiyasini ochadi — "hozir yig'". Bu faqat tajriba uchun; real kodda ishlatilmaydi.

Tajriba: 100 000 mijoz obyekti, har biriga 100 elementli "tarix" massivi biriktiramiz. So'ng mijozlar ro'yxatini o'chiramiz va xotiraga qaraymiz. Faylni xotira.mjs deb saqlang:

js
// node --expose-gc xotira.mjs map   yoki   ... weakmap
const tur = process.argv[2];
const mb = () =>
  (process.memoryUsage().heapUsed / 1048576).toFixed(0);

const jadval = tur === "map" ? new Map() : new WeakMap();
let mijozlar = [];
for (let i = 0; i < 100000; i++) {
  const mijoz = { id: i };
  mijozlar.push(mijoz);
  jadval.set(mijoz, { tarix: new Array(100).fill(i) });
}
gc();
console.log(`${tur}: ${mijozlar.length} mijoz, ${mb()} MB`);

mijozlar = null;
setTimeout(() => {
  gc();
  console.log(`${tur}: mijozlar o'chgach ${mb()} MB`);
  console.log(jadval.constructor.name);
}, 0);

process.argv[2] — buyruq qatoridagi birinchi so'z (map yoki weakmap). process.memoryUsage().heapUsed — JavaScript obyektlari egallagan xotira, baytlarda; uni 1 048 576 ga bo'lib megabaytga aylantirdik. setTimeout bilan ikkinchi o'lchovni keyingi navbatga qoldirdik — buning sababini «WeakRef» bo'limida ko'rasiz. Oxirgi console.log esa jadval ni oxirigacha "kerakli" qilib turadi.

bash
node --expose-gc xotira.mjs map
node --expose-gc xotira.mjs weakmap

Konsolda:

text
map: 100000 mijoz, 95 MB
map: mijozlar o'chgach 95 MB
Map
weakmap: 100000 mijoz, 96 MB
weakmap: mijozlar o'chgach 8 MB
WeakMap

Grafikda bir qarashda ko'ring — o'ng tomondagi ikki ustunga e'tibor bering:

100 000 mijoz va ularning tarixi: xotira
  • Map: mijozlar bor95 MB
  • WeakMap: mijozlar bor96 MB
  • Map: mijozlar o'chgachkalitlar ushlab turibdi95 MB
  • WeakMap: mijozlar o'chgachyozuvlar tozalandi8 MB

Manba: O'lchov: Node 24.21 --expose-gc, Windows 11, 2026-10-04; process.memoryUsage().heapUsed, bir necha marta takrorlandi

Mijozlar bor paytda ikkalasi bir xil — taxminan 95 MB. Ro'yxat o'chgach, Map hech narsani qo'yib yubormadi: 100 000 mijoz va ularning tarixi xotirada qoldi, garchi dastur ularga boshqa yeta olmasa ham. WeakMap esa ularni yozuvlari bilan birga qo'yib yubordi.

Bitta kutilmagan kuzatuv ham bo'ldi. Birinchi urinishda oxirgi console.log(jadval...) qatori yo'q edi va Map ham tozalanib ketdi. V8 jadval ni boshqa hech kim ishlatmasligini o'zi payqab, uni ham axlat deb topdi. Bu — «Qachon o'chiriladi — noma'lum» bo'limidagi gapning tasdig'i: axlat yig'uvchining qarori siz kutgandan aqlliroq bo'lishi mumkin. O'lchovni ishonchli qilish uchun kerakli narsani oxirigacha "tirik" ushlash kerak.

6. WeakRef — kuchsiz havola qo'lda

WeakRef — bitta obyektga kuchsiz havola. U obyektni tirik ushlamaydi. Obyektni olish uchun deref() chaqiriladi: obyekt hali tirik bo'lsa — uni, o'chirilgan bo'lsa — undefined qaytaradi.

js
const rasm = { nom: "osh.webp" };
const havola = new WeakRef(rasm);

console.log(havola.deref() === rasm); // true
console.log(havola.deref()?.nom); // osh.webp

?. — ixtiyoriy zanjir: deref() undefined bersa, xato o'rniga undefined. WeakRef bilan doim shunday yoziladi.

Endi obyektni yo'qotib, --expose-gc bilan kuzatamiz (weakref.mjs):

js
// node --expose-gc weakref.mjs
let rasm = { nom: "osh.webp", baytlar: new Array(1_000_000).fill(0) };
const havola = new WeakRef(rasm);

console.log(havola.deref()?.nom);
rasm = null;
console.log(havola.deref()?.nom);

setTimeout(() => {
  gc();
  console.log(havola.deref()?.nom);
}, 0);

Konsolda:

text
osh.webp
osh.webp
undefined

Ikkinchi qatorga qarang: rasm = null dan keyin ham obyekt joyida. Sababi — standartdagi qoida: deref() bilan bir marta olingan obyekt joriy ish tugaguncha o'chirilmaydi. Aks holda bitta funksiya ichida ikki deref() ikki xil javob berib, kodni chalkashtirardi. "Joriy ish" — hozir yuqoridan pastga bajarilayotgan kod. setTimeout callback'i esa u tugagandan keyin, alohida navbatda ishlaydi (bu tartibni 11-qismda, asinxron JavaScript'da to'liq o'rganamiz). Shuning uchun faqat o'sha navbatda, gc() dan keyin — undefined.

--expose-gc siz gc() mavjud emas:

text
ReferenceError: gc is not defined

Bayroqsiz ham obyekt qachondir tozalanadi. Bu bir zumdan keyin ham, bir soatdan keyin ham, hech qachon ham bo'lishi mumkin. Biz bayroqsiz variantni yuzlab qadam davomida, har qadamda xotirani band qilib kuzatdik — obyekt baribir tirik qoldi.

Qayerda kerak? Katta, lekin qayta yasasa bo'ladigan narsalar keshida: rasm, hisoblangan jadval. "Xotira yetarli bo'lsa — saqlansin, yetmasa — axlat yig'uvchi o'chirsin, men kerak bo'lsa qayta yasayman". Kod doim deref() ning undefined qaytarishiga tayyor bo'lishi shart.

7. FinalizationRegistry — "o'chganda xabar ber"

FinalizationRegistry — obyekt axlat yig'uvchi tomonidan o'chirilgandan keyin callback chaqirishni so'raydigan ro'yxat:

js
// node --expose-gc royxat.mjs
let rasm = { nom: "osh.webp", baytlar: new Array(1_000_000).fill(0) };
const havola = new WeakRef(rasm);
const royxat = new FinalizationRegistry((nom) => {
  console.log(`tozalandi: ${nom}`);
});
royxat.register(rasm, rasm.nom);
rasm = null;

setTimeout(() => {
  gc();
  console.log("gc chaqirildi:", havola.deref()?.nom);
  setTimeout(() => console.log("tugadi", typeof royxat), 100);
}, 0);

Konsolda:

text
gc chaqirildi: undefined
tozalandi: osh.webp
tugadi object

register(obyekt, belgi) — kuzatiladigan obyekt va callback'ga beriladigan belgi. Belgi obyektning o'zi bo'lmasin — aks holda u obyektni tirik ushlab qoladi. Shuning uchun nomni (satrni) berdik. unregister bilan kuzatuvni bekor qilish ham mumkin.

Endi eng muhim tajriba. Oxirgi qatorni console.log("tugadi") ga o'zgartiring — typeof royxat siz — va qayta ishga tushiring:

text
gc chaqirildi: undefined
tugadi

"tozalandi" chiqmadi. Biz uch marta sinab ko'rdik — natija bir xil. Sababi: register dan keyin royxat kodda boshqa ishlatilmadi. Axlat yig'uvchi ro'yxatning o'zini ham keraksiz deb topdi va o'chirdi. O'chgan ro'yxat esa hech kimga xabar bermaydi. Birinchi variantda typeof royxat qatori uni oxirigacha "kerakli" qilib turgan edi.

Bundan tashqari:

  • Callback obyekt o'chgandan keyin, qachondir chaqiriladi — darhol emas.
  • Dastur undan oldin tugasa — umuman chaqirilmaydi.
  • Axlat yig'uvchi obyektni hech qachon o'chirmasa — chaqirilmaydi.

MDN'ning maslahati qisqa: iloji bo'lsa, WeakRef va FinalizationRegistry dan qoching. Muhim tozalash (faylni yopish, ulanishni uzish, taymerni to'xtatish) uchun ularga tayanmang. Buning uchun aniq yo'l bor: removeEventListener, clearTimeout, keyinroq o'rganadigan using. FinalizationRegistry — faqat qo'shimcha kuzatuv uchun: "xotira sizyaptimi?" degan savolni tekshirish, statistika.

Tekshirib ko'ring: Dastur ma'lumotlar bazasi ulanishini FinalizationRegistry callback'ida yopadi. Nima xavf bor?

Javob

Callback kech chaqirilishi, umuman chaqirilmasligi yoki ro'yxatning o'zi tozalanib ketishi mumkin. Ulanishlar yopilmay to'planadi va baza "ulanishlar soni tugadi" deb yangi so'rovlarni rad etadi. Ulanish aniq joyda, aniq kod bilan yopilishi kerak.

8. Qaysi biri qachon

Vazifa Vosita
Kalitlar ro'yxati, sanash, aylanish kerak Map / Set
Obyektga qo'shimcha ma'lumot (metadata), obyekt sizniki emas WeakMap
"Bu obyekt ko'rildimi / ishlandimi?" WeakSet
Obyektga bog'liq kesh, obyekt yo'qolsa kesh ham ketsin WeakMap
Katta, qayta yasaladigan narsani "iloji bo'lsa" saqlash WeakRef (ehtiyot bilan)
Xotira sizishini kuzatish, statistika FinalizationRegistry (faqat qo'shimcha)

Halol gap: bu vositalar har kuni kerak emas — oddiy ilova kodida Map va Set yetadi. Lekin kutubxona kodini o'qiganda va xotira sizishlari ni qidirganda ularsiz tushunib bo'lmaydi.

9. Ko'p uchraydigan xatolar

9.1 Primitiv kalit

new WeakMap().set("osh", 1) — TypeError: Invalid value used as weak map key. WeakSet da — Invalid value used in weak set, WeakRef da — WeakRef: invalid target. Tuzatish: kalit obyekt bo'lsin. Kalit satr yoki son bo'lsa — oddiy Map.

9.2 WeakMap ni aylanishga urinish

for...of, size, keys() — yo'q. Tuzatish: aylanish kerak bo'lsa, demak sizga Map kerak. Yoki kalitlarni alohida massivda saqlang — lekin unda ular kuchli ushlanadi.

9.3 deref() ni tekshirmaslik

havola.deref().nom — obyekt o'chgan bo'lsa, TypeError: Cannot read properties of undefined (reading 'nom'). Tuzatish: havola.deref()?.nom yoki const r = havola.deref(); if (r) { ... }.

9.4 Tozalashni FinalizationRegistry ga yuklash

Callback kafolatlanmagan. Tuzatish: resursni aniq yoping; ro'yxatni faqat kuzatuv uchun ishlating.

9.5 "WeakMap hammasini o'zi tozalaydi" deb o'ylash

WeakMap faqat kalit yo'qolganda yozuvni qo'yib yuboradi. Kalit obyekt boshqa joyda (massivda, global o'zgaruvchida) tirik bo'lsa — yozuv ham qoladi. Qiymat kalitga havola qilsa ham muammo yo'q — V8 buni to'g'ri hal qiladi. Lekin kalitni o'zingiz ushlab tursangiz, WeakMap hech narsa qila olmaydi.

10. Mashqlar

1-mashq (oson): Ko'rishlar soni

korishlar WeakMap ini yarating. korildi(taom) funksiyasi taomning ko'rishlar sonini bittaga oshirsin (birinchi marta — 1). Osh obyektini ikki marta, manti obyektini bir marta "ko'ring" va sonlarini chiqaring.

Yechim
js
const korishlar = new WeakMap();

function korildi(taom) {
  korishlar.set(taom, (korishlar.get(taom) ?? 0) + 1);
}

const osh = { nom: "Osh" };
const manti = { nom: "Manti" };
korildi(osh);
korildi(osh);
korildi(manti);

console.log(korishlar.get(osh), korishlar.get(manti)); // 2 1
console.log(osh); // { nom: 'Osh' }

Map darsidagi sanash qolipi WeakMap da ham ishlaydi. Taom obyektlariga hech narsa yozilmadi — oxirgi qator buni ko'rsatadi.

2-mashq (o'rta): Bir martalik ishlov

ishla(buyurtma) funksiyasini yozing: u buyurtmani konsolga chiqaradi, lekin bitta buyurtma obyektini ikkinchi marta bersangiz, "allaqachon ishlangan" deb chiqaradi. WeakSet ishlating.

Yechim
js
const ishlangan = new WeakSet();

function ishla(buyurtma) {
  if (ishlangan.has(buyurtma)) {
    console.log(`${buyurtma.id}: allaqachon ishlangan`);
    return;
  }
  ishlangan.add(buyurtma);
  console.log(`${buyurtma.id}: ${buyurtma.taom} tayyorlanmoqda`);
}

const b1 = { id: 1, taom: "osh" };
ishla(b1);
ishla({ id: 2, taom: "manti" });
ishla(b1);

Konsolda:

text
1: osh tayyorlanmoqda
2: manti tayyorlanmoqda
1: allaqachon ishlangan

Belgi buyurtmaning o'ziga yozilmadi. Buyurtma kerak bo'lmay qolganda, WeakSet dagi belgisi ham ketadi — kun bo'yi ishlaydigan oshxona dasturida bu muhim.

3-mashq (qiyin): Obyektga bog'langan kesh

Closure amaliyotda darsida keshlangan funksiya yozgan edik — u bitta argumentni kalit qilardi. Endi argument obyekt bo'lgan holat uchun obyektKeshi(hisobla) yozing: natijalar WeakMap da saqlansin, bir xil obyekt uchun hisobla ikkinchi marta chaqirilmasin.

Sinov: savatning jami summasini hisoblovchi funksiyani keshlang va bir savatni ikki marta bering.

Yechim
js
function obyektKeshi(hisobla) {
  const kesh = new WeakMap();
  return (obyekt) => {
    if (!kesh.has(obyekt)) {
      kesh.set(obyekt, hisobla(obyekt));
    }
    return kesh.get(obyekt);
  };
}

let chaqiruvlar = 0;
const jami = obyektKeshi((savat) => {
  chaqiruvlar++;
  return savat.reduce((s, t) => s + t.narx, 0);
});

const savat = [{ narx: 35000 }, { narx: 5000 }];
console.log(jami(savat)); // 40000
console.log(jami(savat)); // 40000
console.log(chaqiruvlar); // 1

kesh closure ichida — tashqaridan yetib bo'lmaydi. Savat obyekti kalit, shuning uchun savat yo'qolsa, uning natijasi ham keshdan ketadi: Map bilan yozilgan kesh esa har bir savatni abadiy eslardi.

Bitta ogohlantirish: bu kesh savat o'zgarmaydi deb hisoblaydi. Savatga yangi taom push qilinsa, obyekt o'sha-o'sha, kesh eski summani beradi. Shuning uchun bunday keshni o'zgarmas (immutable) ma'lumot bilan ishlating — yangi savat — yangi obyekt. Bu mavzuni Memoization darsida davom ettiramiz.

4-mashq: Vazifalar qadami — belgilashlar tarixi (mashqlar repo)

Bu mavzu vazifalar ilovasining xulqini o'zgartirmaydi — qadam mashqlar reposida: kurs/mashqlar/10/47-weakmap/belgilashlar.mjs.

Vazifa necha marta belgilangan va qaytarilganini sanamoqchimiz. Lekin bu son eksportga ({ versiya: 1, vazifalar }) tushmasligi va Vazifa obyektining shakli o'zgarmasligi kerak.

  1. belgilashlar WeakMap i va belgila(vazifa) funksiyasi: bajarildi ni teskarisiga o'zgartiradi va sanoqni oshiradi.
  2. Ikki vazifa bilan sinang, sonlarni va JSON.stringify natijasini chiqaring.
  3. Qo'shimcha (--expose-gc bilan): vazifani ro'yxatdan filter bilan o'chiring va FinalizationRegistry bilan u haqiqatan tozalanganini kuzating.
Yechim

1–2:

js
const belgilashlar = new WeakMap();

function belgila(vazifa) {
  vazifa.bajarildi = !vazifa.bajarildi;
  belgilashlar.set(vazifa, (belgilashlar.get(vazifa) ?? 0) + 1);
}

const vazifalar = [
  { id: 1, matn: "Non olish", bajarildi: false },
  { id: 2, matn: "Choy damlash", bajarildi: false },
];

belgila(vazifalar[0]);
belgila(vazifalar[0]);
belgila(vazifalar[1]);

console.log(belgilashlar.get(vazifalar[0])); // 2
console.log(belgilashlar.get(vazifalar[1])); // 1
console.log(JSON.stringify(vazifalar[0]));

Konsolda:

text
2
1
{"id":1,"matn":"Non olish","bajarildi":false}

Eksport — avvalgidek uch maydon. Ilovadagi Vazifa klassida ham shunday bo'lardi: unga yangi maydon qo'shmasdan, ma'lumot yonida yashaydi.

3 (node --expose-gc belgilashlar.mjs — faylning oxiriga qo'shing):

js
const kuzatuv = new FinalizationRegistry((id) => {
  console.log(`vazifa ${id} xotiradan tozalandi`);
});
kuzatuv.register(vazifalar[1], vazifalar[1].id);

let qolganlar = vazifalar.filter((v) => v.id !== 2);
vazifalar.length = 0;

setTimeout(() => {
  gc();
  setTimeout(() => {
    console.log(qolganlar.length, typeof kuzatuv);
  }, 100);
}, 0);

Konsolda (birinchi uch qatordan keyin):

text
vazifa 2 xotiradan tozalandi
1 object

vazifalar.length = 0 — eski massivni bo'shatdik, endi 2-vazifaga hech qanday kuchli havola yo'q. belgilashlar uni ushlab turmadi — u tozalandi. Oxirgi qatordagi typeof kuzatuv ro'yxatni tirik ushlab turadi, aks holda xabar chiqmasligini «FinalizationRegistry» bo'limida ko'rdik.

Commit (mashqlar repo): 10/47: belgilashlar tarixini WeakMap bilan sana.

11. Real ishda

  • Freymvorklar ichida. Vue — interfeys yasash freymvorki (tayyor tuzilma va qoidalar to'plami; hozir bilish shart emas). Vue 3 ma'lumot o'zgarganda sahifani o'zi yangilaydi. Buning uchun u "obyekt → uni kuzatayotgan funksiyalar" jadvalini WeakMap da saqlaydi. Freymvork va kutubxonalar kodini o'qiganda WeakMap ni tez-tez uchratasiz.
  • Kesh va memoization. Obyekt argumentli funksiyalar natijasi — WeakMap keshida. Kesh obyekt bilan birga yashaydi va o'ladi.
  • Xotira sizishlarini qidirish. Chrome DevTools'ning Memory panelida "Detached" DOM elementlari — ko'pincha Map yoki tinglovchida unutilgan elementlar. Tuzatish — WeakMap yoki aniq tozalash (DevTools: Performance, Memory va Network).
  • Intervyu. "Map va WeakMap farqi nima?", "WeakMap da nega size yo'q?" — tajribali (middle) dasturchi lavozimi intervyusida beriladigan savollar. Javob: kalit faqat obyekt, kuchsiz ushlanadi, aylanib bo'lmaydi, chunki tarkib GC'ga bog'liq.

Xulosa

  • Axlat yig'uvchi ildizdan yetib bo'lmaydigan obyektni o'chiradi; qachon — oldindan aytib bo'lmaydi.
  • Map/Set kalitni kuchli ushlaydi; WeakMap/WeakSet — kuchsiz: obyekt boshqa joyda kerak bo'lmasa, yozuvi bilan ketadi.
  • Kuchsiz to'plamlarda kalit — faqat obyekt; size, aylanish va clear yo'q.
  • WeakMap — begona yoki muzlatilgan obyektga metadata, obyektga bog'liq kesh; WeakSet — "ko'rildi" belgisi.
  • WeakRef.deref() undefined qaytarishi mumkin — doim tekshiring. FinalizationRegistry callback'i kafolatlanmagan — muhim tozalashni unga yuklamang.

Keyingi dars: Symbol — hech qachon to'qnashmaydigan noyob kalitlar: ular nima uchun yaratilgan va obyektga "yashirin" xususiyat qanday qo'shiladi.

Manbalar

  • MDN: "WeakMap", "WeakSet", "WeakRef", "FinalizationRegistry" (ayniqsa "Avoid where possible" bo'limi), "Memory management" — developer.mozilla.org
  • ECMAScript 2025 Language Specification: "WeakRef Objects", "FinalizationRegistry Objects", "Liveness" — tc39.es/ecma262
  • Node.js hujjatlari: process.memoryUsage(), --expose-gc — nodejs.org/api
  • web-features 3.40: weakmap, weakset, weak-references — github.com/web-platform-dx/web-features
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript WeakMap, WeakSet, WeakRef va FinalizationRegistry: kuchsiz havolalar — IlmHamroh