كيف تعمل الإغلاقات في JavaScript: تحليل بمثال من Stack Overflow
\n\nإذا كنت قد بحثت يومًا عن «JavaScript closures» أو قرأت مناقشات على Stack Overflow، فمن المحتمل أنك رأيت السؤال الشهير: «How do JavaScript closures work?». هذا الموضوع جمع آلاف المشاهدات ومئات الإجابات — من شروحات بسيطة إلى تعمق في محرك V8. لماذا؟ لأن الإغلاقات هي أحد تلك المفاهيم التي تبدو في البداية وكأنها سحر، ثم تصبح سلاحك الفائق.
\n\nفي هذه المقالة، لن نعيد سرد النظرية فقط. سننظر تحت غطاء JavaScript، ونحلل ما يحدث في الذاكرة، وأخيرًا سنفهم لماذا يعمل هذا الكود بهذه الطريقة وليس غيرها. هل أنت مستعد؟ لننطلق.
\n\nما هو الإغلاق؟ تعريف بدون حشو
\n\nلنبدأ بالجوهر. الإغلاق (closure) هو دالة «تتذكر» المتغيرات من نطاق الرؤية الخارجي الخاص بها، حتى بعد أن تكون تلك الدالة الخارجية قد أنهت عملها بالفعل.
\n\nيبدو وكأنه تعويذة؟ دعنا ننتقل فورًا إلى الكود. إليك مثالًا كلاسيكيًا ستجده على Stack Overflow:
\n\nfunction createCounter() {\n let count = 0;\n return function() {\n count++;\n return count;\n };\n}\n\nconst counter = createCounter();\nconsole.log(counter()); // 1\nconsole.log(counter()); // 2\nconsole.log(counter()); // 3\n\nهنا، createCounter تُرجع دالة مجهولة. هذه الدالة الداخلية «تُغلق» المتغير count. حتى بعد أن تنتهي createCounter (نعتقد أن جميع المتغيرات داخلها يجب أن تختفي)، يستمر count في الحياة والتغير مع كل استدعاء لـ counter(). هذا هو الإغلاق.
تحت الغطاء: البيئة المعجمية
\n\nلفهم كيفية عمل ذلك، نحتاج إلى النظر داخل محرك JavaScript. كل دالة عند إنشائها تحتوي على خاصية مخفية [[Environment]]. هذه الخاصية تخزن مرجعًا إلى البيئة المعجمية (Lexical Environment) التي تم إنشاء الدالة فيها.
البيئة المعجمية هي ببساطة كائن يحتوي على:
\n- \n
- سجل البيئة (Environment Record) — جميع المتغيرات والدوال المعلنة في نطاق الرؤية هذا. \n
- مرجع إلى البيئة الخارجية (outer) — نطاق الرؤية الأصلي. \n
عندما نستدعي دالة، يتم إنشاء بيئة معجمية جديدة لهذا الاستدعاء. تحصل الدالة الداخلية على الوصول إلى متغيرات الدالة الخارجية من خلال سلسلة نطاقات الرؤية (scope chain).
\n\nلنعد إلى مثالنا. عندما نعلن createCounter، يتم إنشاء بيئة معجمية عامة. داخلها — المتغير createCounter (دالة) و counter (حتى الآن غير معرف).
عندما نستدعي createCounter():
- \n
- يتم إنشاء بيئة معجمية جديدة لهذا الاستدعاء. فيها — المتغير
count = 0. \n - يتم إنشاء دالة مجهولة. يشير
[[Environment]]الخاص بها إلى البيئة الحالية (حيث يعيشcount). \n - يتم إرجاع الدالة وتعيينها إلى
counter. \n
وهنا الجزء الأكثر إثارة للاهتمام: createCounter انتهت، لكن بيئتها المعجمية لا يتم حذفها بواسطة جامع القمامة! لأنه يوجد مرجع إليها — [[Environment]] الخاص بالدالة الداخلية. تظل البيئة «معلقة» في الذاكرة طالما أن الدالة counter نفسها موجودة.
الآن، في كل مرة نستدعي counter()، يتم إنشاء بيئة جديدة لهذا الاستدعاء، والتي ترى عبر السلسلة تلك البيئة القديمة التي تحتوي على count. وهكذا نحصل على تأثير «التذكر».
لماذا هذا مهم؟ سيناريوهات حقيقية
\n\nالإغلاقات ليست مجرد مفهوم أكاديمي. إنها تُستخدم في كل مكان. دعنا نحلل بعض الأمثلة الحية.
\n\n1. المتغيرات الخاصة (التغليف)
\n\nفي JavaScript لا يوجد