المستوى: متوسط (Intermediate). الشرح مبني على فرضية إنك بتكتب JavaScript وفاهم الدوال والـ scope الأساسي. لو لسه مبتدئ، مثال ماكينة التذاكر تحت هيوصّلك الفكرة قبل ما ندخل في التفاصيل العلمية.
Closures في جافاسكريبت: الدالة اللي بتفتكر
بعد ما تخلص المقال ده هتعرف تبني حالة خاصة (private state) في جافاسكريبت بدون class وبدون متغير عام، وتفهم ليه دالة واحدة تقدر تفتكر متغير اتعرّف من زمان واتفترض إنه اختفى. الفكرة دي اسمها Closure، وهي تحت أغلب الأدوات اللي بتستخدمها كل يوم: debounce، الـ event handlers، والـ React hooks.
المشكلة باختصار
القاعدة اللي اتعلمناها: لما الدالة تخلص، المتغيرات اللي جواها بتموت وبيتحرّر مكانها في الذاكرة. ده بيحصل فعلاً في الحالة العادية. لكن في حالة واحدة المتغير بيفضل عايش حتى بعد ما الدالة الأم رجعت: لما تكون في دالة داخلية لسه ماسكة المتغير ده. الدالة الداخلية مع المتغيرات اللي بتفتكرها هي اللي بنسمّيها Closure.
مثال بسيط: ماكينة التذاكر اللي بتفتكر
تخيّل ماكينة تذاكر في عيادة. كل واحد يضغط الزرار يطلعله رقم أكبر من اللي قبله: 1، 2، 3. الماكينة مش بتسأل اللي قبلك وصل لكام، هي جواها عدّاد صغير محفوظ، بتزوّده وتديك النتيجة. الزرار اللي بتضغطه (الدالة الداخلية) بيوصل للعدّاد المخفي (المتغير في الدالة الأم) من غير ما العدّاد ده يبقى ظاهر لأي حد بره الماكينة.
دلوقتي نرجع للتعريف الدقيق: الـ Closure هو دالة + البيئة المعجمية (lexical environment) اللي اتعرّفت جواها. يعني الدالة بتحتفظ بمرجع للمتغيرات اللي كانت في مداها وقت ما اتكتبت، مش وقت ما اتنادت. النقطة دي بالظبط هي اللي بتخدع الناس: المهم مكان تعريف الدالة، مش مكان استدعائها.
الكود اللي بيوريك إنه بيحصل فعلاً
function makeCounter() {
let count = 0; // متغير خاص جوه الدالة الأم
return function () {
count++; // الدالة الداخلية لسه بتشوف count
return count;
};
}
const next = makeCounter(); // makeCounter اشتغلت وخلصت ورجعت
console.log(next()); // 1
console.log(next()); // 2
console.log(next()); // 3
// makeCounter() خلصت من زمان، ومع ذلك count لسه عايش جوه الـ closure
لاحظ: مفيش طريقة توصل لـ count من بره. مفيش next.count. المتغير مخفي تماماً ومحمي، وده بالظبط اللي بيخلّي الـ closures الأساس العملي للـ encapsulation في جافاسكريبت قبل ما الـ private fields توصل للّغة.
كل closure بيحمل حالته الخاصة
كل مرة تنادي makeCounter() بتتولد بيئة جديدة ومتغير count جديد مستقل. يعني العدّادين ميتلخبطوش في بعض: