شرح الإغلاق في جافاسكربت: كيف تعمل Closures وLexical Scope باحتراف
مقدمة: لماذا يختلط مفهوم Closure مع Lexical Scope؟
يُعد مفهوم Closure من أكثر مفاهيم JavaScript التي يشيع حولها الالتباس، وغالباً ما يتم الخلط بينه وبين Lexical Scope. صحيح أن النطاق المعجمي يُعد جزءاً أساسياً من فكرة الإغلاق، لكنه ليس الإغلاق نفسه. وفهم هذا الفرق مهم جداً لكل مطور يرغب في كتابة شيفرة أنظف، أو الاستعداد الجيد للمقابلات التقنية.
إذا كنت تمتلك أساسيات جيدة في الدوال داخل JavaScript، فسيكون فهم هذا الموضوع أسهل بكثير. في هذا المقال سنشرح الفكرة بشكل عملي ومباشر، مع أمثلة تساعدك على استيعاب العلاقة بين Closures وLexical Scope دون تعقيد.

ما الذي ستتعلمه من هذا الشرح؟
- الفرق الحقيقي بين
Lexical ScopeوClosure. - سبب اعتماد
Closureعلى النطاق المعجمي. - كيفية تقديم مثال واضح على
Closureفي المقابلات التقنية. - فهم سلوك المتغيرات داخل الدوال المتداخلة في
JavaScript.
ما هو Lexical Scope في JavaScript؟
يشير Lexical Scope إلى الطريقة التي تستطيع بها الدوال المتداخلة، أو ما يمكن تسميته بالدوال الابنة، الوصول إلى المتغيرات المعرّفة داخل النطاق الأب. بمعنى آخر، موقع تعريف الدالة في الشيفرة هو الذي يحدد ما الذي يمكنها الوصول إليه من متغيرات.
لننظر إلى المثال التالي:
const myFunction = () => {
let myValue = 2;
console.log(myValue);
const childFunction = () => {
console.log(myValue += 1);
};
childFunction();
};
myFunction();
في هذا المثال، تملك الدالة childFunction القدرة على الوصول إلى المتغير myValue، رغم أن هذا المتغير تم تعريفه داخل الدالة الأب myFunction. هذا هو جوهر Lexical Scope: الدالة الداخلية تستطيع الوصول إلى ما هو موجود في النطاق الذي وُلدت بداخله.
كيف يعمل النطاق المعجمي هنا؟
عندما تُنشأ الدالة childFunction داخل myFunction، فإنها تحتفظ بإمكانية الوصول إلى متغيرات هذا النطاق. لذلك، عند تنفيذ childFunction()، يمكنها قراءة قيمة myValue وتعديلها أيضاً.
لكن هذه الحالة وحدها لا تكفي لاعتبارها Closure بالمعنى الكامل. هنا تبدأ نقطة التفريق المهمة.
ما هو Closure في JavaScript؟
يمكن تعريف Closure بأنه دالة تستطيع الوصول إلى النطاق الأب حتى بعد انتهاء تنفيذ الدالة الأب وإغلاقها. هذه الجزئية الأخيرة هي الفارق الجوهري.
لو تأملت التعريف، ستجد أنه يتكون من جزأين:
- دالة لديها وصول إلى النطاق الأب، وهذا يصف
Lexical Scope. - استمرار هذا الوصول حتى بعد انتهاء الدالة الأب، وهذا هو ما يجعلنا أمام
Closureحقيقياً.
مثال عملي على Closure
لنأخذ المثال التالي:
const myFunction = () => {
let myValue = 2;
console.log(myValue);
const childFunction = () => {
console.log(myValue += 1);
};
return childFunction;
};
const result = myFunction();
console.log(result);
result();
result();
result();
شرح المثال خطوة بخطوة
1) الدالة myFunction لم تعد تستدعي childFunction مباشرة
في هذا الإصدار، الدالة myFunction لا تنفذ childFunction داخلياً، بل تقوم بإرجاعها باستخدام return. وهذا تغيير مهم جداً.
2) المتغير result يحتفظ بالدالة المرجعة
عند تنفيذ const result = myFunction(); يتم تشغيل myFunction أولاً، لذلك ستظهر قيمة myValue في وحدة التحكم. لكن الدالة childFunction نفسها لن تُنفذ في تلك اللحظة، بل سيتم إرجاعها وتخزينها داخل المتغير result.
3) انتهاء الدالة الأب لا يعني ضياع المتغيرات
بعد اكتمال تنفيذ myFunction، قد يبدو منطقياً أن متغير myValue انتهى عمره. لكن لأن الدالة المرجعة ما زالت تحتاج إليه، فإن JavaScript تحتفظ به في الذاكرة. وهنا يظهر مفهوم Closure بوضوح.
4) كل استدعاء لـ result() يستخدم نفس البيئة المحيطة
عندما تستدعي result()، فأنت في الواقع تستدعي الدالة التي كانت تُعرف سابقاً باسم childFunction. هذه الدالة ما زالت مرتبطة بالنطاق الذي نشأت داخله، ولذلك يمكنها الوصول إلى myValue وتحديثه في كل مرة.
لهذا ستلاحظ أن القيمة تزداد مع كل استدعاء جديد، لأن الإغلاق يحتفظ بالحالة السابقة ولا يبدأ من الصفر في كل مرة.
الفرق بين Lexical Scope وClosure
| المفهوم | المعنى | متى يظهر؟ |
|---|---|---|
Lexical Scope |
قدرة الدالة الداخلية على الوصول إلى نطاقها الأب | أثناء تعريف الدوال المتداخلة |
Closure |
احتفاظ الدالة بهذا الوصول حتى بعد انتهاء الدالة الأب | عند إرجاع الدالة أو تمريرها مع استمرار اعتمادها على متغيرات خارجية |
بصيغة أبسط:
Lexical Scopeيشرح لماذا تستطيع الدالة الوصول إلى متغير خارجي.Closureيشرح كيف يستمر هذا الوصول حتى بعد انتهاء الدالة التي أنشأت ذلك المتغير.
لماذا يحتاج Closure إلى Lexical Scope؟
لا يمكن أن يوجد Closure من دون Lexical Scope، لأن الإغلاق يعتمد أساساً على وجود دالة داخل دالة أخرى، بحيث تكون الدالة الداخلية قادرة على رؤية المتغيرات المحيطة بها. النطاق المعجمي هو البنية الأساسية، أما Closure فهو النتيجة العملية عندما تستمر هذه العلاقة بعد انتهاء الدالة الأب.
لذلك يمكن القول إن Lexical Scope هو الأساس النظري، بينما Closure هو السلوك الفعلي الذي يظهر عند الاحتفاظ بالدالة واستخدامها لاحقاً.
متى يُستخدم Closure في البرمجة العملية؟
فهم Closures ليس مهماً فقط لاجتياز المقابلات، بل له استخدامات عملية كثيرة في تطوير الواجهات والتطبيقات، ومنها:
- إنشاء بيانات خاصة لا يمكن الوصول إليها مباشرة من خارج الدالة.
- الاحتفاظ بحالة داخلية بين عدة استدعاءات.
- بناء دوال مولّدة مثل العدادات أو المهيئات.
- استخدامها في
callbacks، وevent handlers، والبرمجة غير المتزامنة.
مثال ذهني سريع
تخيل أن الدالة الأب تُنشئ صندوقاً يحتوي على متغيرات، ثم تعطيك دالة داخلية تحمل مفتاح هذا الصندوق. حتى لو غادر صاحب الصندوق المكان، ستظل الدالة الداخلية قادرة على فتحه واستخدام ما بداخله. هذا بالضبط ما يفعله Closure.
كيف تشرح Closure في مقابلة تقنية؟
إذا سُئلت عن Closure في مقابلة عمل، يمكنك تقديم إجابة مختصرة واحترافية مثل الآتي:
Closure هو دالة تحتفظ بإمكانية الوصول إلى المتغيرات الموجودة في نطاقها الخارجي حتى بعد انتهاء تنفيذ الدالة التي أنشأت تلك المتغيرات.
ثم ادعم إجابتك بمثال مشابه للمثال السابق، واشرح أن الدالة المرجعة ما زالت تتذكر المتغير myValue لأن JavaScript احتفظت بالبيئة المحيطة المرتبطة بها.
نصائح مهمة لفهم Closures بعمق
- لا تحاول حفظ التعريف فقط، بل راقب سلوك المتغيرات أثناء تنفيذ الشيفرة.
- جرّب الأمثلة بنفسك وعدّل القيم وعدد الاستدعاءات.
- استخدم
console.log()لتتبع ما يحدث داخل كل دالة. - ركّز على فكرة أن الدالة قد تحمل معها البيئة التي وُلدت فيها.
من الطبيعي أن يبدو هذا المفهوم متقدماً في البداية، لذلك لا تستعجل الفهم. كثير من المطورين لا يدركون Closures بالكامل من أول قراءة، لكن مع الممارسة تصبح الفكرة أكثر وضوحاً وتتحول إلى أداة قوية جداً في كتابة الشيفرة.
الخلاصة التقنية
الفرق بين Lexical Scope وClosure هو أن الأول يحدد صلاحية الوصول إلى المتغيرات وفق مكان تعريف الدالة، بينما الثاني يعبّر عن استمرار هذا الوصول حتى بعد انتهاء النطاق الأب. عملياً، كل Closure يعتمد على Lexical Scope، لكن ليس كل نطاق معجمي يُعد إغلاقاً. إتقان هذا المفهوم يمنحك فهماً أعمق لطريقة عمل الدوال في JavaScript، ويساعدك على كتابة شيفرة أكثر مرونة واحترافية.