🔍 WeakMap في JavaScript: الدليل الشامل للمبتدئين
اليوم سنتعرف على هيكل بيانات مهم ومميز في لغة JavaScript وهو WeakMap. إذا كنت قد تعلمت سابقاً عن الـ Map العادية، فاستعد لاكتشاف نسخة أكثر تخصصاً منها.
🤔 ما هو WeakMap؟
WeakMap هي عبارة عن مجموعة من الأزواج [مفتاح، قيمة] تشبه الـ Map العادية، ولكن مع فرق جوهري واحد:
- المفاتيح في WeakMap يجب أن تكون كائنات (Objects) فقط، ولا يمكن استخدام القيم البدائية (مثل الأرقام، النصوص، etc.) كمفاتيح.
الفكرة الأساسية وراء WeakMap هي توفير طريقة لربط بيانات إضافية بكائن ما دون منع هذا الكائن من حذفه من الذاكرة تلقائياً (عملية Garbage Collection) عندما لا يعود هناك حاجة إليه.
// WeakMap صحيح: المفتاح هو كائن
let myWeakMap = new WeakMap();
let user = { name: "Ahmed" }; // هذا كائن
myWeakMap.set(user, "Extra Data"); // ✅ صحيح
// WeakMap خاطئ: المفتاح ليس كائن
let myWeakMap = new WeakMap();
let myKey = "Name";
myWeakMap.set(myKey, "Data"); // ❌ خطأ! TypeError
⚖️ الفروق الأساسية بين WeakMap و Map العادية
لفهم WeakMap بشكل أعمق، دعنا نقارنها مع الـ Map التقليدية التي تعرفها:
| الميزة | Map العادية | WeakMap |
|---|---|---|
| نوع المفاتيح المسموحة | أي نوع (كائنات، نصوص، أرقام، etc.) | كائنات فقط |
| مرجع الكائن-المفتاح | مرجع "قوي". وجود الكائن في الـ Map يمنع حذفه من الذاكرة. | مرجع "ضعيف". وجود الكائن في الـ WeakMap لا يمنع حذفه. |
| التكرار (Iteration) | يمكنك الحصول على المفاتيح، القيم، والحجم (size). |
لا يمكنك التكرار أو معرفة الحجم. |
| الأسباب | عندما تريد التحكم الكامل بالبيانات ودورة حياتها. | عندما تريد ربط بيانات مؤقتة بكائن دون التأثير على دورة حياته. |
// Map vs WeakMap - مثال عملي
// 1. Map عادية
let regularMap = new Map();
let obj1 = { id: 1 };
regularMap.set(obj1, "Stored in Map");
// حتى لو قمنا بـ `obj1 = null`، الكائن الأصلي سيظل في الذاكرة لأنه موجود في الـ Map.
// 2. WeakMap
let weakMap = new WeakMap();
let obj2 = { id: 2 };
weakMap.set(obj2, "Stored in WeakMap");
obj2 = null; // الكائن { id: 2 } أصبح مؤهلاً للحذف من الذاكرة من قبل Garbage Collector لأن WeakMap لا تمنع ذلك.
🛠️ الطرق الأساسية للتعامل مع WeakMap
WeakMap لها واجهة برمجة بسيطة جداً مقارنة بالـ Map. إليك الطرق المتاحة:
new WeakMap(): لإنشاء WeakMap جديدة. يمكنك تمرير مصفوفة من الأزواج[كائن, قيمة]كمعطى بداية..set(key, value): لتخزين قيمة مرتبطة بالكائن-المفتاح..get(key): لاسترجاع القيمة المرتبطة بكائن-مفتاح معين. ترجعundefinedإذا لم يكن المفتاح موجوداً..has(key): للتحقق مما إذا كان كائن-مفتاح معين موجوداً في الـ WeakMap. ترجعtrueأوfalse..delete(key): لحذف الزوج المرتبط بكائن-مفتاح معين.
// WeakMap إنشاء واستخدام
let cache = new WeakMap();
// كائناتنا (ستكون مفاتيح)
let book1 = { title: "The First Part" };
let book2 = { title: "The Second Part" };
// إضافة بيانات لكل كائن
cache.set(book1, "Content of the first part of the book...");
cache.set(book2, "Content of the second part of the book...");
// الحصول على البيانات
console.log(cache.get(book1)); // "Content of the first part of the book..."
// التحقق من الوجود
console.log(cache.has(book2)); // true
// الحذف
cache.delete(book1);
console.log(cache.has(book1)); // false
ملاحظة مهمة: لا توجد خاصية .size ولا طرق مثل .keys() أو .values() أو .forEach() في WeakMap. هذا لأن قائمة المفاتيح غير ثابتة وقد تتغير في أي لحظة بسبب Garbage Collection.
💡 متى نستخدم WeakMap؟ (أمثلة عملية)
الاستخدام الرئيسي لـ WeakMap هو عندما نريد إرفاق بيانات إضافية بكائن ما، ولكننا نريد أن تختفي هذه البيانات تلقائياً عندما يختفي الكائن الأصلي من الذاكرة.
مثال 1: التخزين المؤقت (Caching) تخيل أن لديك دالة تقوم بمعالجة ثقيلة لكائنات معينة. يمكنك استخدام WeakMap لتخزين النتيجة بحيث إذا طُلب منك معالجة نفس الكائن مرة أخرى، تعيد النتيجة المخزنة. إذا اختفى الكائن من البرنامج، فسيتم مسح نتيجته من الذاكرة تلقائياً.
let calculationCache = new WeakMap();
function heavyCalculation(obj) {
// إذا كانت النتيجة مخزنة مسبقاً، نرجعها
if (calculationCache.has(obj)) {
console.log("جلب النتيجة من الذاكرة المؤقتة!");
return calculationCache.get(obj);
}
// إذا لم تكن مخزنة، نقوم بالعملية الحسابية (نتظاهر أنها عملية بطيئة)
console.log("إجراء عملية حسابية ثقيلة...");
let result = obj.value * 2; // عملية حسابية بسيطة للتمثيل
// نخزن النتيجة في الـ WeakMap مرتبطة بالكائن
calculationCache.set(obj, result);
return result;
}
let dataObj = { value: 10 };
console.log(heavyCalculation(dataObj)); // "إجراء عملية..." ثم 20
console.log(heavyCalculation(dataObj)); // "جلب النتيجة..." ثم 20
// إذا قمنا بـ dataObj = null، فالكائن ونتيجته في الذاكرة المؤقتة سيصبحان مؤهلين للحذف.
مثال 2: تخزين بيانات خاصة (Private Data)
قبل ظهور حقول الفئة الخاصة (بـ #)، كان المطورون يستخدمون WeakMap أحياناً لمحاكاة الخصوصية، حيث تربط بيانات خاصة بكل نسخة (instance) من الكائن دون أن تكون هذه البيانات موجودة كخاصية مباشرة عليه.
🚫 قيود WeakMap (ما لا يمكنك فعله)
كما ذكرنا، WeakMap لديها قيود مقصودة:
- لا يمكنك عرض جميع المفاتيح أو القيم. لا يوجد تكرار.
- لا يمكنك معرفة عدد العناصر. لا توجد خاصية
size. - المفاتيح يجب أن تكون كائنات. لا تقبل نصوصاً أو أرقاماً.
هذه القيود هي بالضبط ما يجعل WeakMap مفيدة في سيناريوهاتها الخاصة، حيث تترك السيطرة على دورة حياة الكائن لنظام JavaScript نفسه.
🎓 اختبر نفسك
التعليقات
شاركنا رأيك أو أسئلتك حول هذا المقال