استكشف WeakMap و WeakSet في JavaScript، أدوات قوية لإدارة الذاكرة بكفاءة. تعلم كيف تمنع تسرب الذاكرة وتحسن أداء تطبيقاتك بأمثلة عملية.
إدارة الذاكرة في JavaScript باستخدام WeakMap و WeakSet: دليل شامل
تُعد إدارة الذاكرة جانبًا حاسمًا في بناء تطبيقات JavaScript قوية وعالية الأداء. يمكن أن تؤدي هياكل البيانات التقليدية مثل الكائنات (Objects) والمصفوفات (Arrays) أحيانًا إلى تسرب الذاكرة، خاصة عند التعامل مع مراجع الكائنات. لحسن الحظ، توفر JavaScript أداتين قويتين هما WeakMap
و WeakSet
، المصممتين لمعالجة هذه التحديات. سيغوص هذا الدليل الشامل في تعقيدات WeakMap
و WeakSet
، موضحًا كيفية عملهما، وفوائدهما، ومقدمًا أمثلة عملية لمساعدتك على الاستفادة منهما بفعالية في مشاريعك.
فهم تسرب الذاكرة في JavaScript
قبل الخوض في WeakMap
و WeakSet
، من المهم فهم المشكلة التي يحلونها: تسرب الذاكرة. يحدث تسرب الذاكرة عندما يقوم تطبيقك بتخصيص ذاكرة ولكنه يفشل في تحريرها وإعادتها إلى النظام، حتى عندما لا تكون تلك الذاكرة مطلوبة بعد الآن. مع مرور الوقت، يمكن أن تتراكم هذه التسريبات، مما يتسبب في تباطؤ تطبيقك وفي النهاية تعطله.
في JavaScript، تتم إدارة الذاكرة إلى حد كبير بشكل تلقائي بواسطة جامع البيانات المهملة (garbage collector). يقوم جامع البيانات المهملة بشكل دوري بتحديد واستعادة الذاكرة التي تشغلها الكائنات التي لم يعد من الممكن الوصول إليها من الكائنات الجذرية (الكائن العام، مكدس الاستدعاءات، إلخ). ومع ذلك، يمكن لمراجع الكائنات غير المقصودة أن تمنع عملية جمع البيانات المهملة، مما يؤدي إلى تسرب الذاكرة. لننظر إلى مثال بسيط:
let element = document.getElementById('myElement');
let data = {
element: element,
value: 'Some data'
};
// ... لاحقًا
// حتى لو تمت إزالة العنصر من DOM، لا يزال 'data' يحتفظ بمرجع له.
// هذا يمنع العنصر من أن يتم جمعه بواسطة جامع البيانات المهملة.
في هذا المثال، يحتفظ الكائن data
بمرجع لعنصر DOM المسمى element
. إذا تمت إزالة element
من DOM ولكن الكائن data
لا يزال موجودًا، فلن يتمكن جامع البيانات المهملة من استعادة الذاكرة التي يشغلها element
لأنه لا يزال يمكن الوصول إليه من خلال data
. هذا مصدر شائع لتسرب الذاكرة في تطبيقات الويب.
مقدمة إلى WeakMap
WeakMap
هي مجموعة من أزواج المفتاح-القيمة حيث يجب أن تكون المفاتيح كائنات ويمكن أن تكون القيم من أي نوع. يشير مصطلح "ضعيف" (weak) إلى حقيقة أن المفاتيح في WeakMap
يتم الاحتفاظ بها بشكل ضعيف، مما يعني أنها لا تمنع جامع البيانات المهملة من استعادة الذاكرة التي تشغلها تلك المفاتيح. إذا لم يعد من الممكن الوصول إلى كائن مفتاح من أي جزء آخر من الكود الخاص بك، وكان يتم الإشارة إليه فقط بواسطة WeakMap
، فإن جامع البيانات المهملة يكون حرًا في استعادة ذاكرة ذلك الكائن. عندما يتم جمع الكائن المفتاح، تصبح القيمة المقابلة في WeakMap
مؤهلة أيضًا للجمع.
الخصائص الرئيسية لـ WeakMap:
- يجب أن تكون المفاتيح كائنات: يمكن استخدام الكائنات فقط كمفاتيح في
WeakMap
. لا يُسمح بالقيم الأولية مثل الأرقام أو السلاسل النصية أو القيم المنطقية. - مراجع ضعيفة: يتم الاحتفاظ بالمفاتيح بشكل ضعيف، مما يسمح بجمع البيانات المهملة عندما لا يكون الكائن المفتاح قابلاً للوصول إليه من مكان آخر.
- لا يمكن التكرار: لا توفر
WeakMap
طرقًا للتكرار على مفاتيحها أو قيمها (مثلforEach
،keys
،values
). هذا لأن وجود هذه الطرق سيتطلب منWeakMap
الاحتفاظ بمراجع قوية للمفاتيح، مما يبطل الغرض من المراجع الضعيفة. - تخزين البيانات الخاصة: غالبًا ما تُستخدم
WeakMap
لتخزين البيانات الخاصة المرتبطة بالكائنات، حيث لا يمكن الوصول إلى البيانات إلا من خلال الكائن نفسه.
الاستخدام الأساسي لـ WeakMap:
إليك مثال بسيط على كيفية استخدام WeakMap
:
let weakMap = new WeakMap();
let element = document.getElementById('myElement');
weakMap.set(element, 'بعض البيانات المرتبطة بالعنصر');
console.log(weakMap.get(element)); // المخرجات: بعض البيانات المرتبطة بالعنصر
// إذا تمت إزالة العنصر من DOM ولم يعد يُشار إليه في أي مكان آخر،
// يمكن لجامع البيانات المهملة استعادة ذاكرته، وسيتم أيضًا إزالة الإدخال في WeakMap.
مثال عملي: تخزين بيانات عناصر DOM
إحدى حالات الاستخدام الشائعة لـ WeakMap
هي تخزين البيانات المرتبطة بعناصر DOM دون منع تلك العناصر من أن يتم جمعها بواسطة جامع البيانات المهملة. لنفترض سيناريو حيث تريد تخزين بعض البيانات الوصفية لكل زر على صفحة ويب:
let buttonMetadata = new WeakMap();
let button1 = document.getElementById('button1');
let button2 = document.getElementById('button2');
buttonMetadata.set(button1, { clicks: 0, label: 'Button 1' });
buttonMetadata.set(button2, { clicks: 0, label: 'Button 2' });
button1.addEventListener('click', () => {
let data = buttonMetadata.get(button1);
data.clicks++;
console.log(`تم النقر على الزر 1 ${data.clicks} مرات`);
});
// إذا تمت إزالة button1 من DOM ولم يعد يُشار إليه في أي مكان آخر،
// يمكن لجامع البيانات المهملة استعادة ذاكرته، وسيتم أيضًا إزالة الإدخال المقابل في buttonMetadata.
في هذا المثال، تخزن buttonMetadata
عدد النقرات والتسمية لكل زر. إذا تمت إزالة زر من DOM ولم يعد هناك مرجع إليه في أي مكان آخر، يمكن لجامع البيانات المهملة استعادة ذاكرته، وسيتم إزالة الإدخال المقابل في buttonMetadata
تلقائيًا، مما يمنع تسرب الذاكرة.
اعتبارات التدويل (Internationalization)
عند التعامل مع واجهات المستخدم التي تدعم لغات متعددة، يمكن أن تكون WeakMap
مفيدة بشكل خاص. يمكنك تخزين البيانات الخاصة باللغة المرتبطة بعناصر DOM:
let localizedStrings = new WeakMap();
let heading = document.getElementById('heading');
// النسخة الإنجليزية
localizedStrings.set(heading, {
en: 'Welcome to our website!',
fr: 'Bienvenue sur notre site web!',
es: '¡Bienvenido a nuestro sitio web!'
});
function updateHeading(locale) {
let strings = localizedStrings.get(heading);
heading.textContent = strings[locale];
}
updateHeading('fr'); // يحدّث العنوان إلى الفرنسية
يسمح لك هذا النهج بربط السلاسل النصية المترجمة بعناصر DOM دون الاحتفاظ بمراجع قوية قد تمنع جمع البيانات المهملة. إذا تمت إزالة العنصر `heading`، فإن السلاسل النصية المترجمة المرتبطة به في `localizedStrings` تصبح أيضًا مؤهلة للجمع.
مقدمة إلى WeakSet
WeakSet
تشبه WeakMap
، لكنها مجموعة من الكائنات بدلاً من أزواج المفتاح-القيمة. مثل WeakMap
، تحتفظ WeakSet
بالكائنات بشكل ضعيف، مما يعني أنها لا تمنع جامع البيانات المهملة من استعادة الذاكرة التي تشغلها تلك الكائنات. إذا لم يعد من الممكن الوصول إلى كائن من أي جزء آخر من الكود الخاص بك وكان يتم الإشارة إليه فقط بواسطة WeakSet
، فإن جامع البيانات المهملة يكون حرًا في استعادة ذاكرة ذلك الكائن.
الخصائص الرئيسية لـ WeakSet:
- يجب أن تكون القيم كائنات: يمكن إضافة الكائنات فقط إلى
WeakSet
. لا يُسمح بالقيم الأولية. - مراجع ضعيفة: يتم الاحتفاظ بالكائنات بشكل ضعيف، مما يسمح بجمع البيانات المهملة عندما لا يكون الكائن قابلاً للوصول إليه من مكان آخر.
- لا يمكن التكرار: لا توفر
WeakSet
طرقًا للتكرار على عناصرها (مثلforEach
،values
). هذا لأن التكرار سيتطلب مراجع قوية، مما يبطل الغرض منها. - تتبع العضوية: غالبًا ما تُستخدم
WeakSet
لتتبع ما إذا كان كائن ينتمي إلى مجموعة أو فئة معينة.
الاستخدام الأساسي لـ WeakSet:
إليك مثال بسيط على كيفية استخدام WeakSet
:
let weakSet = new WeakSet();
let element1 = document.getElementById('element1');
let element2 = document.getElementById('element2');
weakSet.add(element1);
weakSet.add(element2);
console.log(weakSet.has(element1)); // المخرجات: true
console.log(weakSet.has(element2)); // المخرجات: true
// إذا تمت إزالة element1 من DOM ولم يعد يُشار إليه في أي مكان آخر،
// يمكن لجامع البيانات المهملة استعادة ذاكرته، وسيتم إزالته تلقائيًا من WeakSet.
مثال عملي: تتبع المستخدمين النشطين
إحدى حالات الاستخدام لـ WeakSet
هي تتبع المستخدمين النشطين في تطبيق ويب. يمكنك إضافة كائنات المستخدمين إلى WeakSet
عندما يستخدمون التطبيق بنشاط وإزالتهم عندما يصبحون غير نشطين. يتيح لك هذا تتبع المستخدمين النشطين دون منع جمع بياناتهم المهملة.
let activeUsers = new WeakSet();
function userLoggedIn(user) {
activeUsers.add(user);
console.log(`المستخدم ${user.id} سجل الدخول. المستخدمون النشطون: ${activeUsers.has(user)}`);
}
function userLoggedOut(user) {
// لا حاجة للإزالة الصريحة من WeakSet. إذا لم يعد هناك مرجع لكائن المستخدم،
// سيتم جمعه بواسطة جامع البيانات المهملة وإزالته تلقائيًا من WeakSet.
console.log(`المستخدم ${user.id} سجل الخروج.`);
}
let user1 = { id: 1, name: 'Alice' };
let user2 = { id: 2, name: 'Bob' };
userLoggedIn(user1);
userLoggedIn(user2);
userLoggedOut(user1);
// بعد مرور بعض الوقت، إذا لم يعد هناك مرجع لـ user1 في أي مكان آخر، فسيتم جمعه بواسطة جامع البيانات المهملة
// وإزالته تلقائيًا من activeUsers WeakSet.
اعتبارات دولية لتتبع المستخدمين
عند التعامل مع مستخدمين من مناطق مختلفة، يمكن أن يكون تخزين تفضيلات المستخدم (اللغة، العملة، المنطقة الزمنية) إلى جانب كائنات المستخدم ممارسة شائعة. يتيح استخدام WeakMap
بالاقتران مع WeakSet
إدارة فعالة لبيانات المستخدم وحالته النشطة:
let activeUsers = new WeakSet();
let userPreferences = new WeakMap();
function userLoggedIn(user, preferences) {
activeUsers.add(user);
userPreferences.set(user, preferences);
console.log(`المستخدم ${user.id} سجل الدخول مع التفضيلات:`, userPreferences.get(user));
}
let user1 = { id: 1, name: 'Alice' };
let user1Preferences = { language: 'en', currency: 'USD', timeZone: 'America/Los_Angeles' };
userLoggedIn(user1, user1Preferences);
يضمن هذا أن يتم تخزين تفضيلات المستخدم فقط طالما أن كائن المستخدم على قيد الحياة ويمنع تسرب الذاكرة إذا تم جمع كائن المستخدم بواسطة جامع البيانات المهملة.
مقارنة بين WeakMap و Map و WeakSet و Set: الفروقات الرئيسية
من المهم فهم الفروقات الرئيسية بين WeakMap
و Map
، وبين WeakSet
و Set
:
الميزة | WeakMap |
Map |
WeakSet |
Set |
---|---|---|---|---|
نوع المفتاح/القيمة | كائنات فقط (مفاتيح)، أي قيمة (قيم) | أي نوع (مفاتيح وقيم) | كائنات فقط | أي نوع |
نوع المرجع | ضعيف (مفاتيح) | قوي | ضعيف | قوي |
التكرار | غير مسموح به | مسموح به (forEach , keys , values ) |
غير مسموح به | مسموح به (forEach , values ) |
جمع البيانات المهملة | المفاتيح مؤهلة للجمع إذا لم توجد مراجع قوية أخرى | المفاتيح والقيم ليست مؤهلة للجمع طالما أن الـ Map موجودة | الكائنات مؤهلة للجمع إذا لم توجد مراجع قوية أخرى | الكائنات ليست مؤهلة للجمع طالما أن الـ Set موجودة |
متى يجب استخدام WeakMap و WeakSet
تكون WeakMap
و WeakSet
مفيدتين بشكل خاص في السيناريوهات التالية:
- ربط البيانات بالكائنات: عندما تحتاج إلى تخزين بيانات مرتبطة بالكائنات (مثل عناصر DOM، كائنات المستخدم) دون منع تلك الكائنات من أن يتم جمعها بواسطة جامع البيانات المهملة.
- تخزين البيانات الخاصة: عندما تريد تخزين بيانات خاصة مرتبطة بالكائنات يجب أن تكون قابلة للوصول فقط من خلال الكائن نفسه.
- تتبع عضوية الكائن: عندما تحتاج إلى تتبع ما إذا كان كائن ينتمي إلى مجموعة أو فئة معينة دون منع الكائن من أن يتم جمعه.
- التخزين المؤقت للعمليات المكلفة: يمكنك استخدام WeakMap لتخزين نتائج العمليات المكلفة التي يتم إجراؤها على الكائنات. إذا تم جمع الكائن، يتم تجاهل النتيجة المخزنة مؤقتًا تلقائيًا.
أفضل الممارسات لاستخدام WeakMap و WeakSet
- استخدام الكائنات كمفاتيح/قيم: تذكر أن
WeakMap
وWeakSet
يمكنهما فقط تخزين الكائنات كمفاتيح أو قيم، على التوالي. - تجنب المراجع القوية للمفاتيح/القيم: تأكد من أنك لا تنشئ مراجع قوية للمفاتيح أو القيم المخزنة في
WeakMap
أوWeakSet
، لأن هذا سيبطل الغرض من المراجع الضعيفة. - النظر في البدائل: قم بتقييم ما إذا كانت
WeakMap
أوWeakSet
هي الخيار الصحيح لحالة الاستخدام الخاصة بك. في بعض الحالات، قد يكونMap
أوSet
العاديان أكثر ملاءمة، خاصة إذا كنت بحاجة إلى التكرار على المفاتيح أو القيم. - الاختبار بدقة: اختبر الكود الخاص بك بدقة للتأكد من أنك لا تسبب تسربًا للذاكرة وأن
WeakMap
وWeakSet
تعملان كما هو متوقع.
التوافق مع المتصفحات
تدعم جميع المتصفحات الحديثة WeakMap
و WeakSet
، بما في ذلك:
- Google Chrome
- Mozilla Firefox
- Safari
- Microsoft Edge
- Opera
بالنسبة للمتصفحات القديمة التي لا تدعم WeakMap
و WeakSet
بشكل أصلي، يمكنك استخدام polyfills لتوفير الوظائف.
الخاتمة
تُعد WeakMap
و WeakSet
أدوات قيمة لإدارة الذاكرة بكفاءة في تطبيقات JavaScript. من خلال فهم كيفية عملها ومتى يجب استخدامها، يمكنك منع تسرب الذاكرة، وتحسين أداء تطبيقك، وكتابة كود أكثر قوة وقابلية للصيانة. تذكر أن تأخذ في الاعتبار قيود WeakMap
و WeakSet
، مثل عدم القدرة على التكرار على المفاتيح أو القيم، واختر بنية البيانات المناسبة لحالة الاستخدام الخاصة بك. من خلال تبني أفضل الممارسات هذه، يمكنك الاستفادة من قوة WeakMap
و WeakSet
لبناء تطبيقات JavaScript عالية الأداء وقابلة للتوسع عالميًا.