العربية

استكشف 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:

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:

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 مفيدتين بشكل خاص في السيناريوهات التالية:

أفضل الممارسات لاستخدام WeakMap و WeakSet

التوافق مع المتصفحات

تدعم جميع المتصفحات الحديثة WeakMap و WeakSet، بما في ذلك:

بالنسبة للمتصفحات القديمة التي لا تدعم WeakMap و WeakSet بشكل أصلي، يمكنك استخدام polyfills لتوفير الوظائف.

الخاتمة

تُعد WeakMap و WeakSet أدوات قيمة لإدارة الذاكرة بكفاءة في تطبيقات JavaScript. من خلال فهم كيفية عملها ومتى يجب استخدامها، يمكنك منع تسرب الذاكرة، وتحسين أداء تطبيقك، وكتابة كود أكثر قوة وقابلية للصيانة. تذكر أن تأخذ في الاعتبار قيود WeakMap و WeakSet، مثل عدم القدرة على التكرار على المفاتيح أو القيم، واختر بنية البيانات المناسبة لحالة الاستخدام الخاصة بك. من خلال تبني أفضل الممارسات هذه، يمكنك الاستفادة من قوة WeakMap و WeakSet لبناء تطبيقات JavaScript عالية الأداء وقابلة للتوسع عالميًا.