🔍 ما هو WeakSet في JavaScript؟ دليلك الشامل للفهم والتطبيق

اليوم سنتعرف على هيكل بيانات مهم ولكنه أقل شهرة في جافا سكريبت، وهو WeakSet. إذا كنت قد تعلمت من قبل عن Set، فـ WeakSet هو نسخة خاصة منه، مصممة لحل مشكلة محددة تتعلق بإدارة الذاكرة. هيا بنا نبدأ رحلتنا لفهم هذا المفهوم خطوة بخطوة.


🤔 ما هو WeakSet؟ التعريف البسيط

WeakSet هو مجموعة (Collection) خاصة في جافا سكريبت تشبه Set، ولكن مع قيود رئيسية:

  1. يحتوي فقط على كائنات (Objects): لا يمكنك تخزين قيم بدائية (Primitive Values) مثل الأرقام أو النصوص داخل WeakSet.
  2. مراجع ضعيفة (Weak References): الكائنات المخزنة داخل WeakSet هي مراجع ضعيفة. هذا يعني أن وجود الكائن داخل WeakSet لا يمنع محرك جافا سكريبت (محرك V8 في Chrome) من حذف هذا الكائن من الذاكرة إذا لم يعد هناك أي مرجع آخر يشير إليه في برنامجك. هذه العملية تسمى Garbage Collection.

💡 تخيل الأمر هكذا: WeakSet هو مثل قائمة أسماء في دفتر ملاحظاتك بقلم رصاص. إذا اختفى الشخص (الكائن) من حياتك (البرنامج)، يمكنك بسهولة مسح اسمه من الدفتر، ولا يبقى الاسم موجوداً بلا معنى.


⚖️ الفرق بين Set و WeakSet: مقارنة سريعة

لنفهم WeakSet بشكل أفضل، دعنا نقارنه مع Set الذي تعرفه:

الميزة Set WeakSet
أنواع القيم أي نوع (كائنات، أرقام، نصوص، إلخ) كائنات (Objects) فقط
مراجع الذاكرة مراجع قوية (Strong References) مراجع ضعيفة (Weak References)
طرق التكرار keys(), values(), entries(), forEach() لا توجد!
خاصية .size ✅ موجودة غير موجودة
الغرض تخزين مجموعة من القيم الفريدة تتبع الكائنات دون منع حذفها من الذاكرة

الفرق الأكبر هو أن WeakSet لا يمكنك التكرار على عناصره أو معرفة عددها (size)، لأنه مصمم ليكون "خفيفاً" ولا يتدخل في عمل محرك الذاكرة.


🛠️ كيفية إنشاء واستخدام WeakSet

1. الإنشاء (Creation)

يمكنك إنشاء WeakSet فارغ، أو تمرير مجموعة من الكائنات إليه (داخل مصفوفة قابلة للتكرار).

// إنشاء WeakSet فارغ
const weakSet = new WeakSet();

// إنشاء WeakSet مع كائنات ابتدائية
const obj1 = { name: 'Ahmad' };
const obj2 = { id: 123 };
const obj3 = { data: true };

const myWeakSet = new WeakSet([obj1, obj2, obj3]);
console.log(myWeakSet); // WeakSet { <items unknown> }
// لاحظ أن المحتويات لا تظهر مباشرة لأن WeakSet لا يمكن تكراره

2. الطرق الأساسية (Core Methods)

يمتلك WeakSet ثلاث طرق رئيسية فقط:

أ. add(): إضافة كائن
const user = { userId: 101 };
const weakSet = new WeakSet();

weakSet.add(user); // إضافة كائن user إلى weakSet
console.log(weakSet.has(user)); // true
ب. has(): التحقق من وجود كائن
const post = { title: 'درس JavaScript' };
const weakSet = new WeakSet();
weakSet.add(post);

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

const anotherPost = { title: 'درس آخر' };
console.log(weak0Set.has(anotherPost)); // false
ج. delete(): حذف كائن
const tempData = { value: 'مؤقت' };
const weakSet = new WeakSet();
weakSet.add(tempData);

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

weakSet.delete(tempData); // حذف الكائن من weakSet
console.log(weakSet.has(tempData)); // false

🧠 مثال واقعي: تتبع الكائنات التي تمت زيارتها

لنفترض أن لديك تطبيقاً يدير مستندات، وتريد تتبع المستندات التي فتحها المستخدم (لعرض مؤشر "تمت الزيارة" بجانبها) دون أن تمنع هذه القائمة من حذف المستند نفسه من الذاكرة إذا أغلق التطبيق.

// كائنات تمثل مستندات
const doc1 = { id: 1, title: 'التقرير السنوي' };
const doc2 = { id: 2, title: 'الميزانية' };
const doc3 = { id: 3, title: 'خطة المشروع' };

// مجموعة ضعيفة لتتبع المستندات التي تمت زيارتها
const visitedDocuments = new WeakSet();

// المستخدم يفتح مستندين
function openDocument(doc) {
    console.log(`فتح: ${doc.title}`);
    visitedDocuments.add(doc);
}

openDocument(doc1); // فتح: التقرير السنوي
openDocument(doc3); // فتح: خطة المشروع

// التحقق مما إذا تمت زيارة مستند
function isVisited(doc) {
    return visitedDocuments.has(doc);
}

console.log(isVisited(doc1)); // true
console.log(isVisited(doc2)); // false (لم يفتحه المستخدم)

// لاحظ: إذا تم حذف doc1 من البرنامج (مثلاً: تعيينه بـ null)،
// فسيمكن للمحرك حذفه من الذاكرة تلقائياً، ولن يبقى في visitedDocuments.

❌ ما لا يمكنك فعله مع WeakSet

من المهم جداً أن تعرف حدود WeakSet لتجنب الأخطاء:

  1. لا يمكنك تخزين قيم بدائية: سيؤدي إلى خطأ.
    const ws = new WeakSet();
    ws.add('نص'); // TypeError: Invalid value used in weak set
    ws.add(42);   // TypeError: Invalid value used in weak set
    
  2. لا يمكنك التكرار على العناصر: لا توجد طرق مثل forEach أو استخدام for...of.
  3. لا يمكنك معرفة عدد العناصر: خاصية .size غير موجودة.
  4. لا يمكنك استرجاع قائمة بالعناصر: لأنه مرتبط بعملية جمع البيانات المهملة (Garbage Collection).

🎯 متى تستخدم WeakSet؟

استخدم WeakSet في الحالات التالية:

  • عندما تريد تتبع مجموعة من الكائنات (مثل: كائنات DOM التي تم تحريرها، أو المستخدمين النشطين في جلسة معينة).
  • عندما تريد تخزين بيانات وصفية (Metadata) مؤقتة مرتبطة بكائنات، دون أن تسبب تسريباً في الذاكرة (Memory Leak) إذا تم التخلص من الكائن الأصلي.
  • عندما لا تحتاج إلى قائمة بالكائنات، بل فقط إلى القدرة على التحقق من وجود كائن معين فيها.

🧪 تمرين سريع: WeakSet في الممارسة

// لنختبر فهمنا معاً
const button1 = document.createElement('button');
const button2 = document.createElement('button');

const disabledButtons = new WeakSet();

// تعطيل زر وإضافته إلى المجموعة
disabledButtons.add(button1);

// دالة للتحقق مما إذا كان الزر معطلاً
function isButtonDisabled(button) {
    return disabledButtons.has(button);
}

console.log(isButtonDisabled(button1)); // true
console.log(isButtonDisabled(button2)); // false

// إذا أزلنا button1 من الصفحة (مثلاً: button1.remove())
// وأزلنا كل المراجع البرمجية له، يمكن للمحرك حذفه من الذاكرة
// وسيختفي تلقائياً من disabledButtons.

🚀 الخلاصة

WeakSet هو أداة متخصصة في جافا سكريبت تُستخدم لإدارة مجموعات من الكائنات فقط بطريقة ذكية تسمح للمحرك بتنظيف الذاكرة تلقائياً. تذكر دائماً:

  • WeakSet يحتوي على كائنات فقط.
  • WeakSet يستخدم مراجع ضعيفة.
  • WeakSet لا يمكن التكرار عليه أو معرفة حجمه.
  • استخدمه عندما تريد تتبع كائنات دون التسبب في مشاكل بالذاكرة.