Closures في JavaScript: المفهوم اللي لو فهمته صح، نص bugs الـ React هتختفي
لو كتبت setTimeout جوّا for loop ولاقيت إنه بيطبع نفس الرقم 5 مرات بدل 0,1,2,3,4، انت لقيت Closure بنفسك. ولو ما عرفتش تشرح ليه ده بيحصل، يبقى عندك فجوة في فهم أهم concept في JavaScript. المقال ده بيقفل الفجوة دي في 7 دقايق.
المشكلة باختصار
كل مكتبة JavaScript حديثة بتعتمد على Closures: useState في React، useEffect، الـ debounce، الـ throttle، الـ event handlers، حتى Promises. لو ما فهمتش الفكرة، هتلاقي نفسك بتنسخ كود من Stack Overflow بدون ما تعرف ليه شغّال. والأسوأ: لما يكسر، مش هتعرف تصلّحه.
المثال البسيط: خزانة الجيم
تخيّل إنك في جيم. الموظف بيدّيلك خزانة رقم 47 ومفتاح. الخزانة دي حاجتك انت بس. حتى لو الموظف اتغيّر، حتى لو رحت بيتك ورجعت تاني يوم، طول ما المفتاح معاك، الخزانة فاتحالك على نفس المحتوى اللي سبته فيها.
الـ Closure بالظبط كده. الدالة الجوّانية بتمسك مفتاح للمتغيرات اللي كانت موجودة وقت ما اتعرّفت، حتى بعد ما الدالة الخارجية تخلص شغلها وتطلع. المتغيرات دي بتفضل عايشة في الذاكرة طول ما المفتاح ده شغّال.
التعريف العلمي الدقيق
حسب مواصفة ECMA-262 (الـ spec الرسمي للغة)، الـ Closure هو دالة + المرجع البيئي اللي اتعرّفت فيه (Lexical Environment). لما تستدعي دالة جوّا دالة تانية، الـ engine بيبني سلسلة من الـ Environment Records (نوع من scopes الجوّانية)، والدالة الجوّانية بتمسك Reference للسلسلة دي حتى بعد ما الدالة الأم ترجع.
بمعنى تاني: المتغيرات في JavaScript مش بتنمسح لما الدالة تخلص. بتنمسح لما محدش بيشاور عليها. الـ Closure هو اللي بيشاور عليها، فبتفضل.
الكود اللي بيوضّح الفكرة في 8 سطور
function createCounter() {
let count = 0; // متغير محلي
return function () {
count = count + 1; // الدالة الجوّانية بتمسك "مفتاح" لـ count
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
console.log(count); // ReferenceError: count is not defined
لاحظ حاجتين مهمين:
- المتغير جوّا الـ closure. مفيش طريقة توصله من برّا.