استكشف JavaScript WeakMap و WeakSet لإدارة الذاكرة بكفاءة. تعرّف على كيفية تحرير هذه المجموعات تلقائيًا للذاكرة غير المستخدمة، مما يحسن الأداء في التطبيقات المعقدة.
JavaScript WeakMap و WeakSet: إتقان المجموعات ذات الكفاءة في استخدام الذاكرة
تقدم JavaScript العديد من هياكل البيانات المضمنة لإدارة مجموعات البيانات. في حين أن Map و Set القياسيتين توفران أدوات قوية، إلا أنهما قد تؤديان في بعض الأحيان إلى تسرب الذاكرة، خاصة في التطبيقات المعقدة. هذا هو المكان الذي تلعب فيه WeakMap و WeakSet. تقدم هذه المجموعات المتخصصة نهجًا فريدًا لإدارة الذاكرة، مما يسمح لجامع البيانات المهملة في JavaScript باستعادة الذاكرة بكفاءة أكبر.
فهم المشكلة: مراجع قوية
قبل الغوص في WeakMap و WeakSet، دعنا نفهم القضية الأساسية: المراجع القوية. في JavaScript، عندما يتم تخزين كائن كمفتاح في Map أو قيمة في Set، تحتفظ المجموعة بمرجع قوي لهذا الكائن. هذا يعني أنه طالما أن Map أو Set موجودة، لا يمكن لجامع البيانات المهملة استعادة الذاكرة التي يشغلها الكائن، حتى إذا لم يعد الكائن مرجعًا في أي مكان آخر في التعليمات البرمجية الخاصة بك. يمكن أن يؤدي هذا إلى تسرب الذاكرة، خاصة عند التعامل مع مجموعات كبيرة أو طويلة الأمد.
ضع في اعتبارك هذا المثال:
let myMap = new Map();
let key = { id: 1, name: "Example Object" };
myMap.set(key, "Some value");
// Even if 'key' is no longer used directly...
key = null;
// ... the Map still holds a reference to it.
console.log(myMap.size); // Output: 1
في هذا السيناريو، حتى بعد تعيين key على null، لا تزال Map تحتفظ بمرجع للكائن الأصلي. لا يمكن لجامع البيانات المهملة استعادة الذاكرة المستخدمة من قبل هذا الكائن لأن Map يمنعه.
تقديم WeakMap و WeakSet: مراجع ضعيفة للإنقاذ
تعالج WeakMap و WeakSet هذه المشكلة باستخدام مراجع ضعيفة. يسمح المرجع الضعيف بتجميع كائن إذا لم تكن هناك مراجع قوية أخرى إليه. عندما يتم الإشارة إلى المفتاح في WeakMap أو القيمة في WeakSet بشكل ضعيف فقط، يكون جامع البيانات المهملة حرًا في استعادة الذاكرة. بمجرد تجميع الكائن، تتم إزالة الإدخال المقابل تلقائيًا من WeakMap أو WeakSet.
WeakMap: أزواج قيمة-مفتاح مع مفاتيح ضعيفة
WeakMap هي مجموعة من أزواج القيمة-المفتاح حيث يجب أن تكون المفاتيح كائنات. يتم الاحتفاظ بالمفاتيح بشكل ضعيف، مما يعني أنه إذا لم يعد الكائن الرئيسي مرجعًا في مكان آخر، فيمكن تجميعه، وتتم إزالة الإدخال المقابل في WeakMap. من ناحية أخرى، يتم الاحتفاظ بالقيم بمراجع عادية (قوية).
إليك مثال أساسي:
let weakMap = new WeakMap();
let key = { id: 1, name: "WeakMap Key" };
let value = "Associated Data";
weakMap.set(key, value);
console.log(weakMap.get(key)); // Output: "Associated Data"
key = null;
// After garbage collection (which is not guaranteed to happen immediately)...
// weakMap.get(key) might return undefined. This is implementation-dependent.
// We can't directly observe when an entry is removed from a WeakMap, which is by design.
الاختلافات الرئيسية عن Map:
- يجب أن تكون المفاتيح كائنات: لا يمكن استخدام سوى الكائنات كمفاتيح في
WeakMap. القيم الأولية (السلاسل والأرقام والقيم المنطقية والرموز) غير مسموح بها. هذا لأن القيم الأولية غير قابلة للتغيير ولا تتطلب تجميع البيانات المهملة بنفس طريقة الكائنات. - لا يوجد تكرار: لا يمكنك التكرار على المفاتيح أو القيم أو الإدخالات الخاصة بـ
WeakMap. لا توجد طرق مثلforEachأوkeys()أوvalues()أوentries(). هذا لأن وجود هذه الطرق سيتطلب منWeakMapالحفاظ على مرجع قوي لمفاتيحه، مما يبطل الغرض من المراجع الضعيفة. - لا توجد خاصية الحجم: لا تحتوي
WeakMapعلى خاصيةsize. تحديد الحجم سيتطلب أيضًا التكرار على المفاتيح، وهو أمر غير مسموح به. - طرق محدودة: تقدم
WeakMapفقطget(key)وset(key, value)وhas(key)وdelete(key).
WeakSet: مجموعة من الكائنات المحتفظ بها بشكل ضعيف
WeakSet مشابه لـ Set، ولكنه يسمح فقط بتخزين الكائنات كقيم. مثل WeakMap، تحتفظ WeakSet بهذه الكائنات بشكل ضعيف. إذا لم يعد الكائن في WeakSet مرجعًا بقوة في مكان آخر، فيمكن تجميعه، وتقوم WeakSet بإزالة الكائن تلقائيًا.
إليك مثال بسيط:
let weakSet = new WeakSet();
let obj1 = { id: 1, name: "Object 1" };
let obj2 = { id: 2, name: "Object 2" };
weakSet.add(obj1);
weakSet.add(obj2);
console.log(weakSet.has(obj1)); // Output: true
obj1 = null;
// After garbage collection (not guaranteed immediately)...
// weakSet.has(obj1) might return false. This is implementation-dependent.
// We cannot directly observe when an element is removed from a WeakSet.
الاختلافات الرئيسية عن Set:
- يجب أن تكون القيم كائنات: لا يمكن تخزين سوى الكائنات في
WeakSet. القيم الأولية غير مسموح بها. - لا يوجد تكرار: لا يمكنك التكرار على
WeakSet. لا توجد طريقةforEachأو وسائل أخرى للوصول إلى العناصر. - لا توجد خاصية الحجم: لا تحتوي
WeakSetعلى خاصيةsize. - طرق محدودة: تقدم
WeakSetفقطadd(value)وhas(value)وdelete(value).
حالات الاستخدام العملي لـ WeakMap و WeakSet
قد تجعل قيود WeakMap و WeakSet تبدو أقل تنوعًا من نظيراتها الأقوى. ومع ذلك، فإن قدراتها الفريدة في إدارة الذاكرة تجعلها لا تقدر بثمن في سيناريوهات محددة.
1. بيانات تعريف عنصر DOM
تتمثل إحدى حالات الاستخدام الشائعة في ربط بيانات التعريف بعناصر DOM دون تلويث DOM. على سبيل المثال، قد ترغب في تخزين بيانات خاصة بالمكون مرتبطة بعنصر HTML معين. باستخدام WeakMap، يمكنك التأكد من أنه عند إزالة عنصر DOM من الصفحة، يتم أيضًا تجميع بيانات التعريف المرتبطة به، مما يمنع تسرب الذاكرة.
let elementData = new WeakMap();
function initializeComponent(element) {
let componentData = {
// Component-specific data
isActive: false,
onClick: () => { console.log("Clicked!"); }
};
elementData.set(element, componentData);
}
let myElement = document.getElementById("myElement");
initializeComponent(myElement);
// Later, when the element is removed from the DOM:
// myElement.remove();
// The componentData associated with myElement will eventually be garbage collected
// when there are no other strong references to myElement.
في هذا المثال، تقوم elementData بتخزين بيانات التعريف المرتبطة بعناصر DOM. عند إزالة myElement من DOM، يمكن لجامع البيانات المهملة استعادة ذاكرته، وتتم إزالة الإدخال المقابل في elementData تلقائيًا.
2. تخزين نتائج العمليات المكلفة مؤقتًا
يمكنك استخدام WeakMap لتخزين نتائج العمليات المكلفة مؤقتًا بناءً على كائنات الإدخال. إذا لم يعد كائن إدخال قيد الاستخدام، فستتم إزالة النتيجة المخزنة مؤقتًا تلقائيًا من WeakMap، مما يحرر الذاكرة.
let cache = new WeakMap();
function expensiveOperation(input) {
if (cache.has(input)) {
console.log("Cache hit!");
return cache.get(input);
}
console.log("Cache miss!");
// Perform the expensive operation
let result = input.id * 100;
cache.set(input, result);
return result;
}
let obj1 = { id: 5 };
let obj2 = { id: 10 };
console.log(expensiveOperation(obj1)); // Output: Cache miss!, 500
console.log(expensiveOperation(obj1)); // Output: Cache hit!, 500
console.log(expensiveOperation(obj2)); // Output: Cache miss!, 1000
obj1 = null;
// After garbage collection, the entry for obj1 will be removed from the cache.
3. بيانات خاصة للكائنات (WeakMap كحقول خاصة)
قبل تقديم حقول الفئة الخاصة في JavaScript، كانت WeakMap تقنية شائعة لمحاكاة البيانات الخاصة داخل الكائنات. سيتم ربط كل كائن ببياناته الخاصة المخزنة في WeakMap. نظرًا لأن البيانات لا يمكن الوصول إليها إلا من خلال WeakMap والكائن نفسه، فإنها تكون خاصة بشكل فعال.
let _privateData = new WeakMap();
class MyClass {
constructor(secret) {
_privateData.set(this, { secret: secret });
}
getSecret() {
return _privateData.get(this).secret;
}
}
let instance = new MyClass("My Secret Value");
console.log(instance.getSecret()); // Output: My Secret Value
// Trying to access _privateData directly will not work.
// console.log(_privateData.get(instance).secret); // Error (if you somehow had access to _privateData)
// Even if the instance is garbage collected, the corresponding entry in _privateData will be removed.
في حين أن حقول الفئة الخاصة هي الآن النهج المفضل، إلا أن فهم نمط WeakMap هذا لا يزال ذا قيمة للتعليمات البرمجية القديمة وفهم تاريخ JavaScript.
4. تتبع دورة حياة الكائن
يمكن استخدام WeakSet لتتبع دورة حياة الكائنات. يمكنك إضافة كائنات إلى WeakSet عند إنشائها ثم التحقق مما إذا كانت لا تزال موجودة في WeakSet. عند تجميع كائن، تتم إزالته تلقائيًا من WeakSet.
let trackedObjects = new WeakSet();
function trackObject(obj) {
trackedObjects.add(obj);
}
function isObjectTracked(obj) {
return trackedObjects.has(obj);
}
let myObject = { id: 123 };
trackObject(myObject);
console.log(isObjectTracked(myObject)); // Output: true
myObject = null;
// After garbage collection, isObjectTracked(myObject) might return false.
اعتبارات عالمية وأفضل الممارسات
عند العمل مع WeakMap و WeakSet، ضع في اعتبارك أفضل الممارسات العالمية التالية:
- فهم تجميع البيانات المهملة: تجميع البيانات المهملة ليس حتميًا. لا يمكنك التنبؤ بالضبط متى سيتم تجميع كائن. لذلك، لا يمكنك الاعتماد على
WeakMapأوWeakSetلإزالة الإدخالات على الفور عندما لا يعود الكائن مرجعًا. - تجنب الإفراط في الاستخدام: في حين أن
WeakMapوWeakSetمفيدان لإدارة الذاكرة، لا تفرط في استخدامهما. في كثير من الحالات، تكونMapوSetالقياسيتان كافيتين تمامًا وتوفران مزيدًا من المرونة. استخدمWeakMapوWeakSetعندما تحتاج تحديدًا إلى مراجع ضعيفة لتجنب تسرب الذاكرة. - حالات استخدام المراجع الضعيفة: فكر في عمر الكائن الذي تقوم بتخزينه كمفتاح (لـ
WeakMap) أو قيمة (لـWeakSet). إذا كان الكائن مرتبطًا بدورة حياة كائن آخر، فاستخدمWeakMapأوWeakSetلتجنب تسرب الذاكرة. - تحديات الاختبار: يمكن أن يكون اختبار التعليمات البرمجية التي تعتمد على تجميع البيانات المهملة أمرًا صعبًا. لا يمكنك فرض تجميع البيانات المهملة في JavaScript. ضع في اعتبارك استخدام تقنيات مثل إنشاء وتدمير أعداد كبيرة من الكائنات لتشجيع تجميع البيانات المهملة أثناء الاختبار.
- Polyfilling: إذا كنت بحاجة إلى دعم المتصفحات القديمة التي لا تدعم
WeakMapوWeakSetأصليًا، فيمكنك استخدام polyfills. ومع ذلك، قد لا تتمكن polyfills من تكرار سلوك المرجع الضعيف بالكامل، لذا اختبر بدقة.
مثال: ذاكرة التخزين المؤقت للتدويل (i18n)
تخيل سيناريو تقوم فيه ببناء تطبيق ويب مع دعم التدويل (i18n). قد ترغب في تخزين السلاسل المترجمة مؤقتًا بناءً على لغة المستخدم. يمكنك استخدام WeakMap لتخزين ذاكرة التخزين المؤقت، حيث يكون المفتاح هو كائن اللغة والقيمة هي السلاسل المترجمة لتلك اللغة. عندما لا تعود اللغة مطلوبة (على سبيل المثال، يتحول المستخدم إلى لغة مختلفة ولم تعد اللغة القديمة مرجعية)، سيتم تجميع ذاكرة التخزين المؤقت لتلك اللغة تلقائيًا.
let i18nCache = new WeakMap();
function getTranslatedStrings(locale) {
if (i18nCache.has(locale)) {
return i18nCache.get(locale);
}
// Simulate fetching translated strings from a server.
let translatedStrings = {
"greeting": (locale.language === "fr") ? "Bonjour" : "Hello",
"farewell": (locale.language === "fr") ? "Au revoir" : "Goodbye"
};
i18nCache.set(locale, translatedStrings);
return translatedStrings;
}
let englishLocale = { language: "en", country: "US" };
let frenchLocale = { language: "fr", country: "FR" };
console.log(getTranslatedStrings(englishLocale).greeting); // Output: Hello
console.log(getTranslatedStrings(frenchLocale).greeting); // Output: Bonjour
englishLocale = null;
// After garbage collection, the entry for englishLocale will be removed from the cache.
يمنع هذا النهج ذاكرة التخزين المؤقت i18n من النمو إلى أجل غير مسمى واستهلاك الذاكرة المفرطة، خاصة في التطبيقات التي تدعم عددًا كبيرًا من اللغات.
الخلاصة
WeakMap و WeakSet أدوات قوية لإدارة الذاكرة في تطبيقات JavaScript. من خلال فهم قيودها وحالات استخدامها، يمكنك كتابة تعليمات برمجية أكثر كفاءة وقوة تتجنب تسرب الذاكرة. على الرغم من أنها قد لا تكون مناسبة لكل سيناريو، إلا أنها ضرورية للحالات التي تحتاج فيها إلى ربط البيانات بالكائنات دون منع تجميع تلك الكائنات. احتضن هذه المجموعات لتحسين تطبيقات JavaScript الخاصة بك وإنشاء تجربة أفضل لمستخدميك، بغض النظر عن مكان وجودهم في العالم.