لماذا ينظف JavaScript تلقائياً من ورائنا، ولكن لا يزال يتعين علينا فهم كيف يعمل؟
\n\nعندما تكتب بلغة JavaScript، نادراً ما تفكر فيما يحدث للمتغيرات بعد خروجها من نطاق الرؤية. على عكس C أو C++، حيث يتعين عليك يدوياً استدعاء free() أو delete، فإن JavaScript يتحمل عناء العمل القذر لتنظيف الذاكرة. هذا مريح - حتى تأتي اللحظة التي يبدأ فيها تطبيقك في "التباطؤ"، أو استهلاك غيغابايت من RAM، أو الانهيار فجأة مع خطأ Out of Memory.
هل هذا مألوف؟ إذن دعنا نفهم كيف تعمل إدارة الذاكرة في JavaScript بالفعل وما هو جامع القمامة في JavaScript. المفسد: هذا ليس سحراً، بل خوارزمية صارمة يمكن (ويجب) أخذها في الاعتبار عند كتابة الكود.
\n\nكيف يخزن JavaScript البيانات: المكدس والكومة
\n\nقبل الحديث عن جمع القمامة، يجب أن نفهم أين تعيش البيانات. في JavaScript، هناك مخزنان رئيسيان:
\n\n- \n
- المكدس (Stack) - ذاكرة سريعة ولكنها محدودة. يتم تخزين الأنواع البدائية (الأرقام، السلاسل النصية، القيم المنطقية) والمراجع إلى الكائنات هناك. كل استدعاء دالة ينشئ إطاراً جديداً في المكدس. \n
- الكومة (Heap) - منطقة ذاكرة غير منظمة، حيث تعيش الكائنات والمصفوفات والدوال والإغلاقات. هنا يحدث الصراع الرئيسي على الذاكرة. \n
عندما تنشئ كائناً:
\n\nlet user = { name: 'إيفان', age: 30 };\n\n\nالمرجع user يذهب إلى المكدس، بينما الكائن نفسه { name: 'إيفان', age: 30 } يذهب إلى الكومة. طالما أن هناك مرجعاً واحداً على الأقل للكائن، فإنه يبقى حياً. بمجرد أن لا يتبقى أي مراجع، يصبح الكائن "قمامة".
جامع القمامة في JavaScript: البطل الرئيسي
\n\nجامع القمامة في JavaScript (Garbage Collector, GC) هو عملية خلفية تفحص الكومة بشكل دوري وتزيل الكائنات غير القابلة للوصول. يبدو الأمر بسيطاً، لكنه في الواقع حل وسط معقد بين سرعة التنظيف وأداء الكود الخاص بك.
\n\nخوارزمية Mark-and-Sweep (الوسم والكنس)
\n\nتستخدم المحركات الحديثة (V8 في Chrome، SpiderMonkey في Firefox) خوارزمية Mark-and-Sweep. تعمل على مرحلتين:
\n\n- \n
- Mark (الوسم): يبدأ GC من الكائنات الجذرية (الكائن العام، الدوال الحالية، المتغيرات المحلية). يجتاز جميع المراجع من الجذور ويوسم كل كائن قابل للوصول. \n
- Sweep (الكنس): جميع الكائنات غير الموسومة تعتبر قمامة ويتم إزالتها. يتم تحرير الذاكرة. \n
مثال توضيحي:
\n\nlet a = { data: 'A' };\nlet b = { data: 'B' };\na.ref = b; // a يشير إلى b\nb.ref = a; // b يشير إلى a\n\n// الآن لدينا مرجع دائري\na = null; // a لم يعد يشير إلى الكائن\nb = null; // b لم يعد يشير إلى الكائن\n\n\nفي السابق (قبل ظهور Mark-and-Sweep) كانت هذه المراجع الدائرية تؤدي إلى تسربات. لكن GC الحديث يفهم: إذا كان كلا الكائنين غير قابلين للوصول من الجذر، فهما قمامة، حتى لو كانا يشيران إلى بعضهما البعض.
\n\nأجيال الكائنات: الصغيرة والكبيرة
\n\nمعظم الكائنات تعيش لفترة قصيرة. على سبيل المثال، المتغيرات المؤقتة داخل الدوال. لذلك يقسم V8 الكومة إلى أجيال:
\n\n- \n
- Young Generation (الجيل الصغير): الكائنات الجديدة. يتم الجمع هنا بشكل متكرر وسريع (Scavenge - خوارزمية تنسخ الكائنات الحية إلى منطقة أخرى). \n
- Old Generation (الجيل الكبير): الكائنات التي نجت من عدة عمليات جمع. يتم فحصها بشكل أقل، لكن العملية تستغرق وقتاً أطول. \n
هذا النهج يسمح بعدم "تجميد" الصفحة أثناء الفحص الكامل للكومة بأكملها.
\n\nتسربات الذاكرة في JavaScript: كيف تسببها (ولا تفعل)
\n\nحتى مع جامع القمامة الذكي، يمكنك خلق موقف لا يتم فيه تحرير الذاكرة. إليك الأسباب الأكثر شيوعاً لـ تسرب