الميزات "المخفية" للغة
تتناول هذه المقالة موضوعًا محدد التركيز للغاية، والذي نادرًا ما يواجهه معظم المطورين في الممارسة العملية (وقد لا يكونون على علم بوجوده).
نوصي بتخطي هذا الفصل إذا كنت قد بدأت للتو في تعلم JavaScript.
بالتذكير بالمفهوم الأساسي لمبدأ إمكانية الوصول من فصل جمع البيانات المهملة، يمكننا ملاحظة أن محرك JavaScript يضمن الاحتفاظ بالقيم في الذاكرة التي يمكن الوصول إليها أو قيد الاستخدام.
على سبيل المثال:
// يحمل متغير المستخدم مرجعًا قويًا للكائن السماح للمستخدم = { الاسم: "جون" }؛ // دعنا نستبدل قيمة متغير المستخدم المستخدم = فارغ؛ // تم فقدان المرجع وسيتم حذف الكائن من الذاكرة
أو كود مشابه ولكنه أكثر تعقيدًا بعض الشيء مع مرجعين قويين:
// يحمل متغير المستخدم مرجعًا قويًا للكائن السماح للمستخدم = { الاسم: "جون" }؛ // نسخ المرجع القوي للكائن إلى متغير المسؤول دع المشرف = المستخدم؛ // دعنا نستبدل قيمة متغير المستخدم المستخدم = فارغ؛ // لا يزال من الممكن الوصول إلى الكائن من خلال المتغير admin
سيتم حذف الكائن { name: "John" }
من الذاكرة فقط إذا لم تكن هناك إشارات قوية إليه (إذا قمنا أيضًا بالكتابة فوق قيمة المتغير admin
).
يوجد في JavaScript مفهوم يسمى WeakRef
، والذي يتصرف بشكل مختلف قليلاً في هذه الحالة.
المصطلحات: "مرجع قوي"، "مرجع ضعيف"
المرجع القوي - هو مرجع إلى كائن أو قيمة يمنع جامع البيانات المهملة من حذفها. وبالتالي حفظ الشيء أو القيمة في الذاكرة التي يشير إليها.
هذا يعني أن الكائن أو القيمة تظل في الذاكرة ولا يتم جمعها بواسطة جامع البيانات المهملة طالما أن هناك إشارات قوية نشطة إليها.
في JavaScript، المراجع العادية للكائنات هي مراجع قوية. على سبيل المثال:
// يحمل متغير المستخدم مرجعًا قويًا لهذا الكائن السماح للمستخدم = { الاسم: "جون" }؛
المرجع الضعيف - هو مرجع إلى كائن أو قيمة، ولا يمنع جامع البيانات المهملة من حذفهما. يمكن لأداة تجميع البيانات المهملة حذف كائن أو قيمة إذا كانت المراجع الوحيدة المتبقية لها هي مراجع ضعيفة.
ملاحظة الحذر
قبل أن نتعمق في الأمر، تجدر الإشارة إلى أن الاستخدام الصحيح للهياكل التي تمت مناقشتها في هذه المقالة يتطلب تفكيرًا متأنيًا للغاية، ومن الأفضل تجنبها إن أمكن.
WeakRef
– هو كائن يحتوي على مرجع ضعيف لكائن آخر، يسمى target
أو referent
.
تكمن خصوصية WeakRef
في أنها لا تمنع أداة تجميع البيانات المهملة من حذف الكائن المرجعي الخاص بها. بمعنى آخر، لا يحافظ كائن WeakRef
على الكائن referent
حيًا.
الآن لنأخذ متغير user
باعتباره "المرجع" وننشئ منه مرجعًا ضعيفًا إلى متغير admin
. لإنشاء مرجع ضعيف، تحتاج إلى استخدام مُنشئ WeakRef
، وتمرير الكائن الهدف (الكائن الذي تريد مرجعًا ضعيفًا إليه).
في حالتنا - هذا هو متغير user
:
// يحمل متغير المستخدم مرجعًا قويًا للكائن السماح للمستخدم = { الاسم: "جون" }؛ // يحمل المتغير admin إشارة ضعيفة إلى الكائن دع المشرف = جديد WeakRef(user);
يوضح الرسم البياني أدناه نوعين من المراجع: مرجع قوي باستخدام متغير user
ومرجع ضعيف باستخدام متغير admin
:
بعد ذلك، في مرحلة ما، نتوقف عن استخدام متغير user
- حيث يتم استبداله، ويخرج عن النطاق، وما إلى ذلك، مع الاحتفاظ بمثيل WeakRef
في متغير admin
:
// دعنا نستبدل قيمة متغير المستخدم المستخدم = فارغ؛
إن الإشارة الضعيفة إلى كائن ما لا تكفي لإبقائه "حيًا". عندما تكون المراجع الوحيدة المتبقية لكائن مرجعي هي مراجع ضعيفة، يكون لجامع البيانات المهملة الحرية في تدمير هذا الكائن واستخدام ذاكرته لشيء آخر.
ومع ذلك، حتى يتم تدمير الكائن فعليًا، قد يعيده المرجع الضعيف، حتى لو لم يكن هناك مراجع أكثر قوة لهذا الكائن. وهذا يعني أن جسمنا يصبح نوعًا من "قطة شرودنغر" - لا يمكننا أن نعرف على وجه اليقين ما إذا كانت "حية" أم "ميتة":
عند هذه النقطة، للحصول على الكائن من نسخة WeakRef
، سنستخدم طريقة deref()
الخاصة به.
تقوم طريقة deref()
بإرجاع الكائن المرجعي الذي يشير إليه WeakRef
، إذا كان الكائن لا يزال في الذاكرة. إذا تم حذف الكائن بواسطة جامع البيانات المهملة، فسيُرجع التابع deref()
undefined
:
دع المرجع = admin.deref(); إذا (المرجع) { // لا يزال من الممكن الوصول إلى الكائن: يمكننا إجراء أي معالجات به } آخر { // تم جمع الكائن بواسطة جامع البيانات المهملة }
عادةً ما يتم استخدام WeakRef
لإنشاء ذاكرة تخزين مؤقت أو صفائف ترابطية تخزن كائنات كثيفة الاستخدام للموارد. يسمح هذا للمرء بتجنب منع تجميع هذه الكائنات بواسطة أداة تجميع البيانات المهملة فقط بناءً على وجودها في ذاكرة التخزين المؤقت أو المصفوفة النقابية.
أحد الأمثلة الأساسية – هو الموقف عندما يكون لدينا العديد من كائنات الصور الثنائية (على سبيل المثال، ممثلة كـ ArrayBuffer
أو Blob
)، ونريد ربط اسم أو مسار بكل صورة. هياكل البيانات الحالية ليست مناسبة تمامًا لهذه الأغراض:
سيؤدي استخدام Map
لإنشاء ارتباطات بين الأسماء والصور، أو العكس، إلى الاحتفاظ بكائنات الصورة في الذاكرة نظرًا لوجودها في Map
كمفاتيح أو قيم.
WeakMap
غير مؤهل لهذا الهدف أيضًا: لأن الكائنات الممثلة كمفاتيح WeakMap
تستخدم مراجع ضعيفة، وليست محمية من الحذف بواسطة أداة تجميع البيانات المهملة.
لكن في هذه الحالة، نحتاج إلى بنية بيانات تستخدم مراجع ضعيفة في قيمها.
لهذا الغرض، يمكننا استخدام مجموعة Map
، التي تكون قيمها عبارة عن مثيلات WeakRef
تشير إلى الكائنات الكبيرة التي نحتاجها. وبالتالي، فإننا لن نحتفظ بهذه الكائنات الكبيرة وغير الضرورية في الذاكرة لفترة أطول مما ينبغي.
بخلاف ذلك، فهذه طريقة للحصول على كائن الصورة من ذاكرة التخزين المؤقت إذا كان لا يزال من الممكن الوصول إليه. إذا تم جمع البيانات المهملة، فسوف نقوم بإعادة إنشائها أو إعادة تنزيلها مرة أخرى.
بهذه الطريقة، يتم استخدام ذاكرة أقل في بعض المواقف.
يوجد أدناه مقتطف من التعليمات البرمجية يوضح تقنية استخدام WeakRef
.
باختصار، نستخدم Map
تحتوي على مفاتيح سلسلة وكائنات WeakRef
كقيم لها. إذا لم يتم جمع كائن WeakRef
بواسطة أداة تجميع البيانات المهملة، فسنحصل عليه من ذاكرة التخزين المؤقت. بخلاف ذلك، سنعيد تنزيله مرة أخرى ونضعه في ذاكرة التخزين المؤقت لإعادة استخدامه مرة أخرى:
وظيفة جلب الصورة () { // وظيفة مجردة لتحميل الصور ... } وظيفة ضعيفةRefCache(fetchImg) {// (1) const imgCache = new Map(); // (2) العودة (imgName) => {// (3) const castedImg = imgCache.get(imgName); // (4) إذا (cachedImg؟.deref()) {// (5) إرجاع ذاكرة التخزين المؤقت Img?.deref(); } const newImg = fetchImg(imgName); // (6) imgCache.set(imgName, new WeakRef(newImg)); // (7) إرجاع صورة جديدة؛ }; } const getCachedImg = WeakRefCache(fetchImg);
دعونا نتعمق في تفاصيل ما حدث هنا:
weakRefCache
- هي دالة ذات ترتيب أعلى تأخذ دالة أخرى، fetchImg
، كوسيطة. في هذا المثال، يمكننا إهمال الوصف التفصيلي لوظيفة fetchImg
، حيث يمكن أن تكون أي منطق لتنزيل الصور.
imgCache
- عبارة عن ذاكرة تخزين مؤقت للصور، تقوم بتخزين النتائج المخزنة مؤقتًا لوظيفة fetchImg
، في شكل مفاتيح سلسلة (اسم الصورة) وكائنات WeakRef
كقيم لها.
قم بإرجاع دالة مجهولة تأخذ اسم الصورة كوسيطة. سيتم استخدام هذه الوسيطة كمفتاح للصورة المخزنة مؤقتًا.
محاولة الحصول على النتيجة المخزنة مؤقتًا من ذاكرة التخزين المؤقت باستخدام المفتاح المقدم (اسم الصورة).
إذا كانت ذاكرة التخزين المؤقت تحتوي على قيمة للمفتاح المحدد، ولم يتم حذف كائن WeakRef
بواسطة أداة تجميع البيانات المهملة، فقم بإرجاع النتيجة المخزنة مؤقتًا.
إذا لم يكن هناك إدخال في ذاكرة التخزين المؤقت بالمفتاح المطلوب، أو قامت طريقة deref()
بإرجاع undefined
(مما يعني أن كائن WeakRef
قد تم تجميعه من القمامة)، فإن وظيفة fetchImg
تقوم بتنزيل الصورة مرة أخرى.
ضع الصورة التي تم تنزيلها في ذاكرة التخزين المؤقت ككائن WeakRef
.
الآن لدينا مجموعة Map
، حيث المفاتيح - هي أسماء الصور كسلاسل، والقيم - هي كائنات WeakRef
التي تحتوي على الصور نفسها.
تساعد هذه التقنية على تجنب تخصيص كمية كبيرة من الذاكرة للكائنات كثيفة الاستخدام للموارد، والتي لم يعد يستخدمها أحد. كما أنه يوفر الذاكرة والوقت في حالة إعادة استخدام الكائنات المخزنة مؤقتًا.
فيما يلي تمثيل مرئي لما يبدو عليه هذا الرمز:
لكن هذا التنفيذ له عيوبه: بمرور الوقت، سيتم ملء Map
بسلاسل كمفاتيح، والتي تشير إلى WeakRef
، الذي تم بالفعل جمع البيانات المهملة عن كائنه المرجعي:
إحدى طرق التعامل مع هذه المشكلة هي مسح ذاكرة التخزين المؤقت بشكل دوري ومسح الإدخالات "الميتة". هناك طريقة أخرى وهي استخدام أدوات الإنهاء، والتي سنستكشفها لاحقًا.
حالة استخدام أخرى لـ WeakRef
هي تتبع كائنات DOM.
دعونا نتخيل سيناريو حيث تتفاعل بعض التعليمات البرمجية أو المكتبة التابعة لجهة خارجية مع العناصر الموجودة على صفحتنا طالما أنها موجودة في DOM. على سبيل المثال، يمكن أن يكون أداة مساعدة خارجية للمراقبة والإبلاغ عن حالة النظام (يسمى عادةً "المسجل" - وهو برنامج يرسل رسائل إعلامية تسمى "السجلات").
مثال تفاعلي:
نتيجة
Index.js
Index.css
Index.html
const startMessagesBtn = document.querySelector('.start-messages'); // (1) const CloseWindowBtn = document.querySelector('.window__button'); // (2) const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) startMessagesBtn.addEventListener('click', () => { // (4) startMessages(windowElementRef); startMessagesBtn.disabled = true; }); CloseWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) const startMessages = (العنصر) => { const timerId = setInterval(() => { // (6) إذا (element.deref()) {// (7) const payload = document.createElement("p"); payload.textContent = `الرسالة: حالة النظام جيدة: ${new Date().toLocaleTimeString()}`; element.deref().append(payload); } آخر {// (8) تنبيه("تم حذف العنصر."); // (9) ClearInterval(timerId); } }, 1000); };
.برنامج { العرض: فليكس؛ الاتجاه المرن: العمود؛ الفجوة: 16 بكسل؛ } رسائل البداية { العرض: محتوى مناسب؛ } .نافذة { العرض: 100%؛ الحدود: 2 بكسل صلب #464154؛ الفائض: مخفي؛ } .نافذة__رأس { الموقف: لزجة. الحشو: 8 بكسل؛ العرض: فليكس؛ ضبط المحتوى: مسافة بين؛ محاذاة العناصر: مركز؛ لون الخلفية: #736e7e؛ } .نافذة__العنوان { الهامش: 0; حجم الخط: 24 بكسل؛ وزن الخط: 700؛ اللون: أبيض؛ تباعد الحروف: 1 بكسل؛ } .نافذة__زر { الحشو: 4 بكسل؛ الخلفية: #4f495c؛ الخطوط العريضة: لا شيء؛ الحدود: 2 بكسل صلب #464154؛ اللون: أبيض؛ حجم الخط: 16 بكسل؛ المؤشر: المؤشر؛ } .نافذة__الجسم { الارتفاع: 250 بكسل؛ الحشو: 16 بكسل؛ الفائض: التمرير؛ لون الخلفية: #736e7e33؛ }
<!DOCTYPE HTML> <html لانج="ar"> <الرأس> <ميتا محارف = "utf-8"> <link rel="stylesheet" href="index.css"> <title>WeakRef DOM Logger</title> </الرأس> <الجسم> <div class="app"> <button class="start-messages">ابدأ بإرسال الرسائل</button> <div class="window"> <div class="window__header"> <p class="window__title">الرسائل:</p> <button class="window__button">إغلاق</button> </div> <div class="window__body"> لا توجد رسائل. </div> </div> </div> <script type="module" src="index.js"></script> </الجسم> </html>
عند النقر فوق الزر "بدء إرسال الرسائل"، في ما يسمى "نافذة عرض السجلات" (عنصر من فئة .window__body
)، تبدأ الرسائل (السجلات) في الظهور.
ولكن، بمجرد حذف هذا العنصر من DOM، يجب أن يتوقف المُسجل عن إرسال الرسائل. لإعادة إنتاج إزالة هذا العنصر، فقط انقر فوق الزر "إغلاق" في الزاوية اليمنى العليا.
من أجل عدم تعقيد عملنا، وعدم إخطار كود الطرف الثالث في كل مرة يتوفر فيها عنصر DOM الخاص بنا، وعندما لا يكون كذلك، سيكون كافيًا إنشاء مرجع ضعيف له باستخدام WeakRef
.
بمجرد إزالة العنصر من DOM، سيلاحظه المسجل ويتوقف عن إرسال الرسائل.
الآن دعونا نلقي نظرة فاحصة على الكود المصدري ( tab index.js
):
احصل على عنصر DOM الخاص بزر "بدء إرسال الرسائل".
احصل على عنصر DOM للزر "إغلاق".
احصل على عنصر DOM لنافذة عرض السجلات باستخدام مُنشئ new WeakRef()
. بهذه الطريقة، يحتفظ المتغير windowElementRef
بمرجع ضعيف لعنصر DOM.
أضف مستمع الأحداث على زر "بدء إرسال الرسائل"، المسؤول عن بدء تشغيل المُسجل عند النقر عليه.
أضف مستمعًا للحدث على زر "إغلاق"، وهو المسؤول عن إغلاق نافذة عرض السجلات عند النقر عليه.
استخدم setInterval
لبدء عرض رسالة جديدة كل ثانية.
إذا كان عنصر DOM الخاص بنافذة عرض السجلات لا يزال قابلاً للوصول ومحفوظًا في الذاكرة، فقم بإنشاء رسالة جديدة وإرسالها.
إذا قام التابع deref()
بإرجاع قيمة undefined
، فهذا يعني أن عنصر DOM قد تم حذفه من الذاكرة. في هذه الحالة، يتوقف المسجل عن عرض الرسائل ويمسح الموقت.
alert
، والذي سيتم استدعاؤه، بعد حذف عنصر DOM لنافذة عرض السجلات من الذاكرة (أي بعد النقر فوق الزر "إغلاق"). لاحظ أن الحذف من الذاكرة قد لا يحدث على الفور، لأنه يعتمد فقط على الآليات الداخلية لجامع البيانات المهملة.
لا يمكننا التحكم في هذه العملية مباشرة من الكود. ومع ذلك، على الرغم من ذلك، لا يزال لدينا خيار فرض جمع البيانات المهملة من المتصفح.
في Google Chrome، على سبيل المثال، للقيام بذلك، تحتاج إلى فتح أدوات المطور ( Ctrl + Shift + J على نظامي التشغيل Windows/Linux أو Option + ⌘ + J على نظام التشغيل macOS)، وانتقل إلى علامة التبويب "الأداء"، وانقر على الزر زر رمز سلة المهملات – "جمع البيانات المهملة":
هذه الوظيفة مدعومة في معظم المتصفحات الحديثة. بعد اتخاذ الإجراءات، سيتم تشغيل alert
على الفور.
الآن حان الوقت للحديث عن اللمسات النهائية. قبل أن ننتقل، دعونا نوضح المصطلحات:
رد اتصال التنظيف (الصيغة النهائية) - هي وظيفة يتم تنفيذها عندما يتم حذف كائن مسجل في FinalizationRegistry
من الذاكرة بواسطة أداة تجميع البيانات المهملة.
والغرض منه هو توفير القدرة على إجراء عمليات إضافية تتعلق بالكائن بعد حذفه نهائيًا من الذاكرة.
التسجيل (أو FinalizationRegistry
) - هو كائن خاص في JavaScript يدير تسجيل الكائنات وإلغاء تسجيلها وعمليات رد اتصال التنظيف الخاصة بها.
تسمح هذه الآلية بتسجيل كائن لتتبعه وربط رد اتصال التنظيف به. إنها في الأساس بنية تقوم بتخزين المعلومات حول الكائنات المسجلة وعمليات رد الاتصال الخاصة بالتنظيف، ثم تقوم تلقائيًا باستدعاء عمليات الاسترجاعات هذه عند حذف الكائنات من الذاكرة.
لإنشاء مثيل لـ FinalizationRegistry
، فإنه يحتاج إلى استدعاء المُنشئ الخاص به، والذي يأخذ وسيطة واحدة - رد اتصال التنظيف (Finalizer).
بناء الجملة:
وظيفة تنظيفCallback(heldValue) { // تنظيف رمز رد الاتصال } const Register = new FinalizationRegistry(cleanupCallback);
هنا:
cleanupCallback
– رد اتصال التنظيف الذي سيتم استدعاؤه تلقائيًا عند حذف كائن مسجل من الذاكرة.
heldValue
- القيمة التي تم تمريرها كوسيطة لاستدعاء التنظيف. إذا كانت heldValue
عبارة عن كائن، فسيحتفظ السجل بمرجع قوي إليه.
registry
- مثيل FinalizationRegistry
.
طرق FinalizationRegistry
:
register(target, heldValue [, unregisterToken])
- يستخدم لتسجيل الكائنات في السجل.
target
- الكائن الذي يتم تسجيله للتتبع. إذا كان target
عبارة عن مجموعة من البيانات المهملة، فسيتم استدعاء رد اتصال التنظيف مع استخدام heldValue
كوسيطة له.
unregisterToken
الاختياري – رمز مميز لإلغاء التسجيل. يمكن تمريره لإلغاء تسجيل كائن قبل أن يقوم جامع البيانات المهملة بحذفه. عادة، يتم استخدام الكائن target
كـ unregisterToken
، وهي الممارسة القياسية.
unregister(unregisterToken)
- يتم استخدام طريقة unregister
لإلغاء تسجيل كائن من السجل. يستغرق الأمر وسيطة واحدة - unregisterToken
(رمز إلغاء التسجيل الذي تم الحصول عليه عند تسجيل الكائن).
والآن دعنا ننتقل إلى مثال بسيط. دعونا نستخدم كائن user
المعروف بالفعل وننشئ مثيلاً لـ FinalizationRegistry
:
السماح للمستخدم = { الاسم: "جون" }؛ تسجيل ثابت = FinalizationRegistry جديد((heldValue) => { console.log(`${heldValue} تم جمعه بواسطة جامع البيانات المهملة.`); });
بعد ذلك، سنقوم بتسجيل الكائن الذي يتطلب رد اتصال التنظيف عن طريق استدعاء طريقة register
:
Register.register(user, user.name);
لا يحتفظ السجل بإشارة قوية إلى الكائن الذي يتم تسجيله، لأن هذا من شأنه أن يتعارض مع غرضه. إذا احتفظ السجل بمرجع قوي، فلن يتم جمع البيانات المهملة أبدًا.
إذا تم حذف الكائن بواسطة جامع البيانات المهملة، فقد يتم استدعاء رد اتصال التنظيف الخاص بنا في وقت ما في المستقبل، مع تمرير heldValue
إليه:
// عندما يتم حذف كائن المستخدم بواسطة جامع البيانات المهملة، ستتم طباعة الرسالة التالية في وحدة التحكم: "لقد تم جمع جون من قبل جامع القمامة."
هناك أيضًا مواقف، حتى في التطبيقات التي تستخدم رد اتصال التنظيف، هناك احتمال ألا يتم استدعاؤها.
على سبيل المثال:
عندما ينهي البرنامج عمله بالكامل (على سبيل المثال، عند إغلاق علامة تبويب في المتصفح).
عندما لا يكون من الممكن الوصول إلى مثيل FinalizationRegistry
نفسه من خلال كود JavaScript. إذا كان الكائن الذي يقوم بإنشاء مثيل FinalizationRegistry
خارج النطاق أو تم حذفه، فقد لا يتم أيضًا استدعاء عمليات رد اتصال التنظيف المسجلة في هذا السجل.
وبالعودة إلى مثال ذاكرة التخزين المؤقت الضعيفة ، يمكننا ملاحظة ما يلي:
على الرغم من أن القيم الموجودة في WeakRef
قد تم جمعها بواسطة جامع البيانات المهملة، إلا أنه لا تزال هناك مشكلة "تسرب الذاكرة" في شكل المفاتيح المتبقية، والتي تم جمع قيمها بواسطة جامع البيانات المهملة.
فيما يلي مثال محسّن للتخزين المؤقت باستخدام FinalizationRegistry
:
وظيفة جلب الصورة () { // وظيفة مجردة لتحميل الصور ... } وظيفة ضعيفةRefCache(fetchImg) { const imgCache = new Map(); تسجيل const = new FinalizationRegistry((imgName) => { // (1) const castedImg = imgCache.get(imgName); if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); }); العودة (imgName) => { const castedImg = imgCache.get(imgName); إذا (cachedImg؟.deref()) { إرجاع ذاكرة التخزين المؤقت Img?.deref(); } const newImg = fetchImg(imgName); imgCache.set(imgName, new WeakRef(newImg)); Register.register(newImg, imgName); // (2) إرجاع صورة جديدة؛ }; } const getCachedImg = WeakRefCache(fetchImg);
لإدارة تنظيف إدخالات ذاكرة التخزين المؤقت "الميتة"، عندما يتم جمع كائنات WeakRef
المرتبطة بواسطة أداة تجميع البيانات المهملة، نقوم بإنشاء سجل تنظيف FinalizationRegistry
.
النقطة المهمة هنا هي أنه في رد اتصال التنظيف، يجب التحقق مما إذا تم حذف الإدخال بواسطة أداة تجميع البيانات المهملة ولم تتم إعادة إضافته، حتى لا يتم حذف الإدخال "المباشر".
بمجرد تنزيل القيمة الجديدة (الصورة) ووضعها في ذاكرة التخزين المؤقت، نقوم بتسجيلها في سجل Finalizer لتتبع كائن WeakRef
.
يحتوي هذا التنفيذ على أزواج المفاتيح/القيمة الفعلية أو "المباشرة" فقط. في هذه الحالة، يتم تسجيل كل كائن WeakRef
في FinalizationRegistry
. وبعد أن يتم تنظيف الكائنات بواسطة جامع البيانات المهملة، سيحذف رد اتصال التنظيف جميع القيم undefined
.
فيما يلي تمثيل مرئي للكود المحدث:
يتمثل أحد الجوانب الرئيسية للتنفيذ المحدث في أن أدوات الإنهاء النهائية تسمح بإنشاء عمليات متوازية بين البرنامج "الرئيسي" وعمليات رد اتصال التنظيف. في سياق جافا سكريبت، البرنامج "الرئيسي" - هو رمز جافا سكريبت الخاص بنا، والذي يتم تشغيله وتنفيذه في تطبيقنا أو صفحة الويب الخاصة بنا.
وبالتالي، من لحظة وضع علامة على الكائن للحذف بواسطة أداة تجميع البيانات المهملة، وحتى التنفيذ الفعلي لاستدعاء التنظيف، قد تكون هناك فجوة زمنية معينة. من المهم أن نفهم أنه خلال هذه الفجوة الزمنية، يمكن للبرنامج الرئيسي إجراء أي تغييرات على الكائن أو حتى إعادته إلى الذاكرة.
لهذا السبب، في رد اتصال التنظيف، يجب علينا التحقق لمعرفة ما إذا كان قد تمت إضافة إدخال مرة أخرى إلى ذاكرة التخزين المؤقت بواسطة البرنامج الرئيسي لتجنب حذف الإدخالات "المباشرة". وبالمثل، عند البحث عن مفتاح في ذاكرة التخزين المؤقت، هناك احتمال أن يتم حذف القيمة بواسطة أداة تجميع البيانات المهملة، ولكن لم يتم تنفيذ رد اتصال التنظيف بعد.
تتطلب مثل هذه المواقف اهتمامًا خاصًا إذا كنت تعمل مع FinalizationRegistry
.
بالانتقال من النظرية إلى التطبيق، تخيل سيناريو من الحياة الواقعية، حيث يقوم المستخدم بمزامنة صوره على جهاز محمول مع بعض الخدمات السحابية (مثل iCloud أو Google Photos)، ويريد مشاهدتها من أجهزة أخرى. بالإضافة إلى الوظيفة الأساسية لعرض الصور، توفر هذه الخدمات الكثير من الميزات الإضافية، على سبيل المثال:
تحرير الصور وتأثيرات الفيديو.
إنشاء "الذكريات" والألبومات.
مونتاج الفيديو من سلسلة من الصور.
… وأكثر من ذلك بكثير.
هنا، كمثال، سوف نستخدم تطبيقًا بدائيًا إلى حد ما لمثل هذه الخدمة. النقطة الأساسية – هي إظهار سيناريو محتمل لاستخدام WeakRef
و FinalizationRegistry
معًا في الحياة الواقعية.
هنا هو ما يبدو:
على الجانب الأيسر، توجد مكتبة سحابية للصور (يتم عرضها كصور مصغرة). يمكننا تحديد الصور التي نحتاجها وإنشاء مجموعة مجمعة، من خلال النقر على الزر "إنشاء مجموعة مجمعة" الموجود على الجانب الأيمن من الصفحة. بعد ذلك، يمكن تنزيل الصورة المجمعة الناتجة كصورة.
لزيادة سرعة تحميل الصفحة، سيكون من المعقول تنزيل الصور المصغرة وعرضها بجودة مضغوطة . ولكن، لإنشاء مجموعة مجمعة من الصور المحددة، قم بتنزيلها واستخدامها بجودة بالحجم الكامل .
أدناه، يمكننا أن نرى أن الحجم الجوهري للصور المصغرة هو 240 × 240 بكسل. تم اختيار الحجم عمدا لزيادة سرعة التحميل. علاوة على ذلك، لا نحتاج إلى صور بالحجم الكامل في وضع المعاينة.
لنفترض أننا بحاجة إلى إنشاء مجموعة من 4 صور: نختارها، ثم نضغط على زر "إنشاء مجموعة". في هذه المرحلة، تقوم وظيفة weakRefCache
المعروفة بالفعل بالتحقق مما إذا كانت الصورة المطلوبة موجودة في ذاكرة التخزين المؤقت. إذا لم يكن الأمر كذلك، فإنه يقوم بتنزيله من السحابة ويضعه في ذاكرة التخزين المؤقت لمزيد من الاستخدام. يحدث هذا لكل صورة محددة:
من خلال الاهتمام بالمخرجات الموجودة في وحدة التحكم، يمكنك معرفة الصور التي تم تنزيلها من السحابة - تتم الإشارة إلى ذلك بواسطة FETCHED_IMAGE . وبما أن هذه هي المحاولة الأولى لإنشاء مجمعة، فهذا يعني أنه في هذه المرحلة كانت "ذاكرة التخزين المؤقت الضعيفة" لا تزال فارغة، وتم تنزيل جميع الصور من السحابة ووضعها فيها.
ولكن، إلى جانب عملية تنزيل الصور، هناك أيضًا عملية تنظيف الذاكرة بواسطة أداة تجميع البيانات المهملة. وهذا يعني أن الكائن المخزن في ذاكرة التخزين المؤقت، والذي نشير إليه باستخدام مرجع ضعيف، يتم حذفه بواسطة جامع البيانات المهملة. ويتم تنفيذ أداة الإنهاء الخاصة بنا بنجاح، وبالتالي حذف المفتاح الذي تم من خلاله تخزين الصورة في ذاكرة التخزين المؤقت. CLEANED_IMAGE يخطرنا بذلك:
بعد ذلك، ندرك أننا لا نحب الصورة المجمعة الناتجة، ونقرر تغيير إحدى الصور وإنشاء صورة جديدة. للقيام بذلك، ما عليك سوى إلغاء تحديد الصورة غير الضرورية، وتحديد صورة أخرى، ثم النقر فوق الزر "إنشاء صورة مجمعة" مرة أخرى:
لكن هذه المرة لم يتم تنزيل جميع الصور من الشبكة، وتم أخذ إحداها من ذاكرة التخزين المؤقت الضعيفة: تخبرنا رسالة CACHED_IMAGE بذلك. هذا يعني أنه في وقت إنشاء الصورة المجمعة، لم يكن جامع البيانات المهملة قد حذف صورتنا بعد، وقمنا بأخذها بجرأة من ذاكرة التخزين المؤقت، وبالتالي تقليل عدد طلبات الشبكة وتسريع الوقت الإجمالي لعملية إنشاء الصورة المجمعة:
دعونا "نلعب" أكثر قليلاً، من خلال استبدال إحدى الصور مرة أخرى وإنشاء مجموعة جديدة:
وهذه المرة كانت النتيجة أكثر إثارة للإعجاب. من بين الصور الأربع المحددة، تم التقاط 3 منها من ذاكرة التخزين المؤقت الضعيفة، وكان لا بد من تنزيل صورة واحدة فقط من الشبكة. وكان الانخفاض في تحميل الشبكة حوالي 75٪. مثير للإعجاب، أليس كذلك؟
بالطبع، من المهم أن نتذكر أن مثل هذا السلوك غير مضمون، ويعتمد على التنفيذ والتشغيل المحددين لأداة تجميع البيانات المهملة.
وبناءً على ذلك، يظهر على الفور سؤال منطقي تمامًا: لماذا لا نستخدم ذاكرة تخزين مؤقت عادية، حيث يمكننا إدارة كياناتها بأنفسنا، بدلاً من الاعتماد على أداة تجميع البيانات المهملة؟ هذا صحيح، في الغالبية العظمى من الحالات ليست هناك حاجة لاستخدام WeakRef
و FinalizationRegistry
.
هنا، قمنا ببساطة بعرض تطبيق بديل لوظيفة مماثلة، باستخدام نهج غير تافه مع ميزات لغوية مثيرة للاهتمام. ومع ذلك، لا يمكننا الاعتماد على هذا المثال، إذا كنا بحاجة إلى نتيجة ثابتة ويمكن التنبؤ بها.
يمكنك فتح هذا المثال في وضع الحماية.
WeakRef
– مصمم لإنشاء مراجع ضعيفة للكائنات، مما يسمح بحذفها من الذاكرة بواسطة جامع البيانات المهملة إذا لم تعد هناك مراجع قوية لها. يعد هذا مفيدًا لمعالجة الاستخدام الزائد للذاكرة وتحسين استخدام موارد النظام في التطبيقات.
FinalizationRegistry
- هي أداة لتسجيل عمليات الاسترجاعات، التي يتم تنفيذها عند تدمير الكائنات التي لم تعد مرجعًا قويًا لها. يسمح هذا بتحرير الموارد المرتبطة بالكائن أو تنفيذ العمليات الضرورية الأخرى قبل حذف الكائن من الذاكرة.