Series: JavaScript Basics Lesson 36

WeakSet

WeakSet nima, nega faqat obyekt qabul qiladi, WeakMap bilan farqi, GC-friendly "marking" pattern va brand-check misoli graf aylanish (traverse) orqali.

36-dars

WeakSet

1. WeakSet nima — Set va WeakMap'ning kesishmasi

WeakSet — bu Setning obyekt-only, weak-reference versiyasi. Agar WeakMap — "Map, lekin key'lar weak" bo'lsa, WeakSet — "Set, lekin barcha elementlar weak".

const weakSet = new WeakSet();

const obj1 = { name: 'Ali' };
weakSet.add(obj1);

console.log(weakSet.has(obj1)); // true

2. Nega faqat obyekt qabul qilinadi (xuddi WeakMap kabi)

weakSet.add({ id: 1 }); // ✅ ishlaydi
weakSet.add('string');  // ❌ TypeError: Invalid value used in weak set
weakSet.add(42);        // ❌ TypeError

Sabab — xuddi WeakMap'dagi bir xil mantiq: "weak reference" tushunchasi faqat heap'da yashaydigan, GC tomonidan track qilinadigan narsalar (obyektlar) uchun ma'noga ega. Primitivlar (string, number) — qiymat orqali ishlaydi, GC "reachability" grafida alohida node sifatida ishtirok etmaydi, shuning uchun ularga "weak" yoki "strong" tushunchasi umuman qo'llanmaydi.


3. V8 ichida — GC bilan aloqasi (WeakMap bilan bir xil mexanizm)

let obj = { data: 'katta' };
const weakSet = new WeakSet([obj]);

obj = null; // so'nggi strong referens o'chirildi
ROOT
  × (obj o'zgaruvchisi endi yo'q)

weakSet ──╌╌╌(weak reference)╌╌╌→ { data: 'katta' }

→ GC MARK bosqichida bu weak pointerni HISOBGA OLMAYDI
→ obyekt "yetib bo'lmaydigan" deb topiladi
→ GC uni YIG'IB OLADI
→ weakSet ichidagi yozuv HAM avtomatik yo'qoladi

Bu — aynan WeakMap'da ko'rgan mexanizm, faqat bu yerda key-value juftlik o'rniga, faqat bitta qiymat (obyektning o'zi) saqlanadi — "key" va "value" farqi yo'q, Set'da bo'lgani kabi.


4. WeakSet metodlari — juda cheklangan (ataylab)

const weakSet = new WeakSet();
const obj = {};

weakSet.add(obj);
weakSet.has(obj);     // true
weakSet.delete(obj);  // true

// LEKIN bular YO'Q:
weakSet.size;                // undefined
weakSet.forEach(...);        // TypeError
for (const x of weakSet) {}  // TypeError — iterable EMAS
[...weakSet];                // TypeError

Nega? Xuddi WeakMap'da ko'rganimiz sababga ko'ra: GC istalgan, nedeterministik paytda elementlarni yig'ib olishi mumkin. Agar size yoki iteratsiya mavjud bo'lganida, natija GC qachon ishlashiga bog'liq, bashorat qilib bo'lmaydigan bo'lib qolardi. Shu sababli spetsifikatsiya bu imkoniyatlarni ataylab olib tashlagan.


5. WeakSet vs WeakMap — qachon qaysi birini ishlatish

// WeakMap — OBYEKTGA BOG'LIQ QO'SHIMCHA MA'LUMOT kerak bo'lganda
const userMetadata = new WeakMap();
userMetadata.set(userObj, { lastLogin: Date.now(), role: 'admin' });
// "Bu OBYEKT haqida QANDAY MA'LUMOT bor?" — javob KERAK

// WeakSet — FAQAT "BU OBYEKT RO'YXATDA BORMI?" savoliga javob kerak bo'lganda
const processedObjects = new WeakSet();
processedObjects.add(userObj);
// "Bu OBYEKT allaqachon ISHLANGANMI?" — ha/yo'q, QO'SHIMCHA MA'LUMOT KERAK EMAS
WeakMap WeakSet
Saqlaydi Key-Value juftlik (obyektga bog'liq ma'lumot) Faqat obyektlarning o'zi
Savol turi "Bu obyekt haqida nima bor?" "Bu obyekt bormi/yo'qmi?"
Amaliy misol DOM elementga metadata biriktirish "Ko'rilgan/ishlangan" obyektlarni belgilash

6. Amaliy holat 1 — obyektni "belgilash" (marking), memory leak'siz

const processedItems = new WeakSet();

function processItem(item) {
  if (processedItems.has(item)) {
    console.log('Allaqachon ishlangan, o\'tkazib yuboramiz');
    return;
  }

  // ... qandaydir og'ir ishlov berish
  processedItems.add(item);
}

let item1 = { id: 1, data: 'katta ma\'lumot' };
processItem(item1); // ishlanadi, belgilanadi
processItem(item1); // "Allaqachon ishlangan" — o'tkazib yuboriladi

item1 = null; // referens o'chirildi
// GC item1'ni yig'ib oladi, WeakSet'dagi belgi HAM avtomatik yo'qoladi
// — hech qanday "unutilgan belgi" xotirada qolib ketmaydi!

Agar bu yerda oddiy Set ishlatilganida, item1 = null qilingandan keyin ham, processedItems uni hali ham ushlab turardi — bu memory leak bo'lardi (ayniqsa minglab item ishlansa, hech biri hech qachon tozalanmasdi).


7. Amaliy holat 2 — "brand check" (obyekt haqiqiy turini tekshirish)

const validInstances = new WeakSet();

class SecureToken {
  constructor(value) {
    this.value = value;
    validInstances.add(this); // yaratilgan paytda "haqiqiy" deb belgilanadi
  }

  static isValid(token) {
    return validInstances.has(token);
  }
}

const t1 = new SecureToken('abc123');
const fake = { value: 'abc123' }; // qo'lda soxta obyekt yasaldi

console.log(SecureToken.isValid(t1));   // true
console.log(SecureToken.isValid(fake)); // false — soxta obyekt, WeakSet'da yo'q!

Bu — #field in obj "brand check" g'oyasiga juda yaqin, faqat bu yerda WeakSet orqali amalga oshirilgan. Farqi: #field in obj — bu til darajasidagi (built-in) brand check, WeakSet esa — qo'lda, dasturchi tomonidan qurilgan shunga o'xshash mexanizm (asosan private field mavjud bo'lmagan eski kodlarda ishlatilgan).


8. Nega WeakSet ko'p dasturchilar tomonidan kamroq ishlatiladi (real amaliyot)

WeakMap — juda ko'p ishlatiladi (masalan private data, DOM metadata, memoization cache). WeakSet esa nisbatan kamroq — chunki "obyekt bormi-yo'qmi" degan oddiy savol ko'pincha boshqa yo'llar bilan (masalan #field yoki oddiy instanceof) hal qilinadi. Lekin "vaqtinchalik, GC-friendly belgilash (marking)" kerak bo'lgan holatlarda — WeakSet hali ham eng to'g'ri, xavfsiz tanlov bo'lib qoladi.

const visited = new WeakSet(); // TASHQARIDA — traverse() dan MUSTAQIL yashaydi

function traverse(node) {
  if (visited.has(node)) return;
  visited.add(node);

  for (const child of node.children || []) {
    traverse(child);
  }
}

let bigTree = buildHugeTree();
traverse(bigTree);

Bu holatda visited — traverse funksiyasidan tashqarida, masalan modul darajasida yashaydi, va funksiya tugagandan keyin ham xotirada qoladi (chunki uni tashqi kod hali ham ushlab turibdi).

Endi savol: agar visited — Set bo'lsa nima farq qiladi?

let someNode = bigTree.children[500];
someNode.disconnect(); // asl daraxtdan uzildi
bigTree.children = bigTree.children.filter(c => c !== someNode);
someNode = null; // so'nggi tashqi referens ham o'chirildi

// visited HALI HAM 'someNode'ga ishora qilib turibdi (chunki u avval traverse() ichida qo'shilgan edi)

Agar visited — oddiy Set bo'lsa:

someNode — daraxtdan uzildi, tashqi referens ham yo'q
LEKIN — visited (Set) ichida hali ham STRONG pointer bor
→ GC uni yig'a OLMAYDI — bu node ABADIY xotirada qolib ketadi, garchi
  hech qayerda kerak bo'lmasa ham — MEMORY LEAK!

Agar visited — WeakSet bo'lsa:

someNode — daraxtdan uzildi, tashqi referens ham yo'q
visited (WeakSet) ichida faqat WEAK pointer bor (GC buni hisobga OLMAYDI)
→ GC uni ERKIN yig'ib oladi
→ visited ichidagi mos yozuv HAM avtomatik yo'qoladi

9. Xulosa jadval — barcha to'rttasini yakuniy solishtirish

Map Set WeakMap WeakSet
Saqlaydi Key-Value Faqat qiymat Key(obyekt)-Value Faqat obyekt
Key/element turi Har qanday Har qanday Faqat obyekt Faqat obyekt
Referens turi Strong Strong Weak (key uchun) Weak
size ✅ ✅ ❌ ❌
Iteratsiya ✅ ✅ ❌ ❌
GC bilan avtomatik tozalanish ❌ ❌ ✅ ✅
Amaliy holat Umumiy kolleksiya Noyob qiymatlar Private data, DOM metadata Marking, brand check

Xulosa

WeakSet — Setning faqat obyekt qabul qiladigan, weak-reference varianti: u elementlarga (WeakMap'dagi key kabi) GC "mark" bosqichida hisobga olinmaydigan kuchsiz pointer orqali ishora qiladi, shuning uchun elementga boshqa hech qanday strong referens qolmasa, GC uni erkin yig'ib oladi va WeakSet'dagi yozuv avtomatik yo'qoladi — bu uni "vaqtinchalik obyektlarni memory leak'siz belgilash" (masalan graf aylanishida "ko'rilganlar" ro'yxati) uchun ideal qiladi, lekin xuddi WeakMap kabi, nedeterministik tozalanish sababli size va iteratsiya imkoniyatlaridan mahrum.