المستوى: مبتدئ
لو دالة في JavaScript رجّعت دالة جوّاها، والدالة الجوّانية فضلت تشوف متغيرات الدالة الأم بعد ما خلصت، الموضوع مش بَج في المتصفح. ده اسمه Closure، وهيغيّر طريقة كتابتك للكود لما تفهمه بالظبط.
Closures في JavaScript: شرح من الصفر بمثال حقيقي
المشكلة باختصار
الكود ده بيشتغل، وأغلب المبتدئين بيتفاجأوا منه:
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
السؤال المنطقي: المتغير count اتعرّف جوّه دالة makeCounter. الدالة دي خلصت من زمان لمّا اتنفذت أول مرة. ليه count لسه عايش وبيتزود في كل استدعاء؟
المثال الأبسط: خزنة البنك
تخيّل خزنة جوّه بنك. آخر اليوم، الموظفين بيمشوا والبنك بيقفل أبوابه. بس فيه مفتاح اتسلّم لشخص واحد بَره. الشخص ده يقدر يفتح الخزنة لوحده ويعدّل اللي جواها، حتى لو البنك مقفول والكاشير راح بيته.
الدالة الجوّانية في الكود فوق هي الشخص اللي معاه المفتاح. count هو الخزنة. makeCounter هي البنك اللي قفل أبوابه. البنك خلص شغل، بس الخزنة لسه شغّالة طول ما المفتاح موجود.
التعريف العلمي بدقة
Closure هو: دالة + الـ lexical environment اللي اتعرّفت فيه. يعني الدالة بتشيل معاها reference لكل المتغيرات الخارجية اللي شافتها وقت تعريفها، حتى لو الـ scope الخارجي خلص تنفيذه.
محرك V8 (اللي بيشغّل Node.js و Chrome) لمّا بيلاقي دالة جوّانية بتستخدم متغير من الدالة الأم، بيخزّن المتغير ده في الـ heap بدل الـ stack. الـ heap ميتسحبش بمجرد ما الدالة الأم ترجع. عشان كده المتغير بيفضل موجود طول ما فيه أي reference للدالة الجوّانية.
سيناريو واقعي: عدّاد لكل مستخدم بدون global state
لو عندك تطبيق Node.js فيه 50 ألف مستخدم متصل، وعايز كل واحد يكون له عدّاد رسائل خاص بدون ما تستخدم متغير global أو Map كبيرة:
function userSession(userId) {
let messages = 0;
const startedAt = Date.now();
return {
increment: () => ++messages,
snapshot: () => ({ userId, messages, uptimeMs: Date.now() - startedAt }),
};
}
const ahmed = userSession('user_42');
ahmed.increment(); // 1
ahmed.increment(); // 2
ahmed.snapshot(); // { userId: 'user_42', messages: 2, uptimeMs: 14 }