المستوى: مبتدئ
لو دالة في JavaScript رجّعت دالة تانية، والدالة التانية لسه فاكرة المتغيرات اللي كانت في الدالة الأم بعد ما الأم خلصت شغلها واتمسحت — اللي بيحصل ده اسمه Closure. وده مش feature غريبة في اللغة، ده الأساس اللي مبني عليه useState في React و debounce و كل private state في الـ JavaScript modules.
Closures في JavaScript: المفهوم اللي بيخلّي الكود "يفتكر"
المشكلة باختصار
تخيّل إنك عايز تبني عدّاد. كل ما الفنكشن تتنادى، الرقم يزيد واحد. لو حطيت المتغير برّه الفنكشن، أي حد في الكود يقدر يعدّل عليه. لو حطيته جوّه الفنكشن، هيتمسح كل مرة وتبدأ من الصفر. الـ Closure بيحلّ التناقض ده: المتغير محفوظ، بس مش متاح إلا للدالة اللي اتعملت معاه.
مثال للمبتدئ: خزنة البنك
تخيّل إنك دخلت بنك وفتحت خزنة. الخزنة فيها فلوس، ومعاك مفتاح. خرجت من البنك، البنك قفل أبوابه، الموظفين راحوا بيوتهم — بس مفتاحك لسه شغّال على الخزنة بتاعتك بالظبط. الخزنة دي زي scope الدالة الأم، والمفتاح زي الدالة اللي رجّعتها. الـ JavaScript مش بيمسح المتغيرات اللي لسه في إيد دالة شغّالة.
التعريف الدقيق
الـ Closure في ECMAScript عبارة عن دالة + مرجع لكل المتغيرات اللي كانت متاحة وقت ما الدالة دي اتعرّفت (lexical environment). المعيار الرسمي بيسمّي ده [[Environment]] internal slot. كل ما تنفّذ الدالة، الـ engine بيرجع للـ environment ده ويقرأ منه القيم.
كود شغّال: عدّاد آمن في 12 سطر
function makeCounter() {
let count = 0;
return {
increment() { count += 1; return count; },
decrement() { count -= 1; return count; },
value() { return count; }
};
}
const c = makeCounter();
c.increment(); // 1
c.increment(); // 2
c.value(); // 2
console.log(c.count); // undefined — مفيش وصول مباشرالمتغير count مش property على object، ومش global. هو محبوس جوّه closure، ومحدش يقدر يعدّله غير الـ 3 دوال اللي رجعت من makeCounter. ده بالظبط نفس النمط اللي React بيستخدمه في useState تحت الغطا.
الفخ الكلاسيكي: var داخل for loop
ركز في الكود ده. هو فخ بيقع فيه كل مبتدئ مرة على الأقل:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// المتوقع: 0 1 2
// الفعلي: 3 3 3