أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالمناهج والباقات
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • المناهج والباقات
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالمناهجالمدونةالدخول
البرمجة بالعربي

Closures في جافاسكريبت للمتوسط: ليه الدالة بتفضل فاكرة المتغير بعد ما الدالة الأم خلصت

متوسط٢٧ يونيو ٢٠٢٦5 دقائق قراءة
Closures في جافاسكريبت للمتوسط: ليه الدالة بتفضل فاكرة المتغير بعد ما الدالة الأم خلصت
المستوى: متوسط (Intermediate). الشرح مبني على فرضية إنك بتكتب JavaScript وفاهم الدوال والـ scope الأساسي. لو لسه مبتدئ، مثال ماكينة التذاكر تحت هيوصّلك الفكرة قبل ما ندخل في التفاصيل العلمية.

Closures في جافاسكريبت: الدالة اللي بتفتكر

بعد ما تخلص المقال ده هتعرف تبني حالة خاصة (private state) في جافاسكريبت بدون class وبدون متغير عام، وتفهم ليه دالة واحدة تقدر تفتكر متغير اتعرّف من زمان واتفترض إنه اختفى. الفكرة دي اسمها Closure، وهي تحت أغلب الأدوات اللي بتستخدمها كل يوم: debounce، الـ event handlers، والـ React hooks.

المشكلة باختصار

القاعدة اللي اتعلمناها: لما الدالة تخلص، المتغيرات اللي جواها بتموت وبيتحرّر مكانها في الذاكرة. ده بيحصل فعلاً في الحالة العادية. لكن في حالة واحدة المتغير بيفضل عايش حتى بعد ما الدالة الأم رجعت: لما تكون في دالة داخلية لسه ماسكة المتغير ده. الدالة الداخلية مع المتغيرات اللي بتفتكرها هي اللي بنسمّيها Closure.

مثال بسيط: ماكينة التذاكر اللي بتفتكر

تخيّل ماكينة تذاكر في عيادة. كل واحد يضغط الزرار يطلعله رقم أكبر من اللي قبله: 1، 2، 3. الماكينة مش بتسأل اللي قبلك وصل لكام، هي جواها عدّاد صغير محفوظ، بتزوّده وتديك النتيجة. الزرار اللي بتضغطه (الدالة الداخلية) بيوصل للعدّاد المخفي (المتغير في الدالة الأم) من غير ما العدّاد ده يبقى ظاهر لأي حد بره الماكينة.

دلوقتي نرجع للتعريف الدقيق: الـ Closure هو دالة + البيئة المعجمية (lexical environment) اللي اتعرّفت جواها. يعني الدالة بتحتفظ بمرجع للمتغيرات اللي كانت في مداها وقت ما اتكتبت، مش وقت ما اتنادت. النقطة دي بالظبط هي اللي بتخدع الناس: المهم مكان تعريف الدالة، مش مكان استدعائها.

الكود اللي بيوريك إنه بيحصل فعلاً

JavaScript
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 جديد مستقل. يعني العدّادين ميتلخبطوش في بعض:

JavaScript
const a = makeCounter();
const b = makeCounter();
console.log(a()); // 1
console.log(a()); // 2
console.log(b()); // 1  عدّاد b مستقل تماماً عن a

سيناريو واقعي: debounce لصندوق البحث

لو عندك صندوق بحث بيضرب الـ API في كل ضغطة كيبورد، كلمة من 8 حروف ممكن تطلّع 8 طلبات أو أكتر، وفي الكتابة السريعة بتوصل لحوالي 40 طلب على الفاضي للاستعلام الواحد. الـ debounce بيستنى المستخدم يبطّل كتابة 300 ميلي ثانية وبعدها يبعت طلب واحد بس. الحيلة كلها إننا محتاجين نفتكر الـ timer بين كل نداء، وده شغل الـ closure:

JavaScript
function debounce(fn, ms) {
  let timer;                 // محفوظ بين كل نداء بفضل الـ closure
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), ms);
  };
}

const search = debounce((q) => fetch('/api/search?q=' + q), 300);
// في تجربة على input بـ 40 ضغطة سريعة: الطلبات نزلت من 40 لـ 1

من غير الـ closure كنت هتضطر تحط الـ timer في متغير عام، وده بيكسر أول ما يبقى عندك أكتر من صندوق بحث في نفس الصفحة، لأن كلهم هيتشاركوا نفس المؤقّت.

الفخ: الـ closure ممكن يتحوّل لتسريب ذاكرة

لإن الـ closure بيمسك مرجع للمتغيرات، أي متغير ماسكه بيفضل في الذاكرة طول ما الدالة عايشة. لو المتغير ده حاجة كبيرة، انت بتدفع رام من غير ما تحس:

JavaScript
function holdBigArray() {
  const big = new Array(1000000).fill(0); // حوالي 8MB
  return () => big.length;                // الـ closure ماسك big كله
}

const refs = [];
for (let i = 0; i < 50; i++) refs.push(holdBigArray());
// 50 closure في حوالي 8MB يساوي تقريباً 400MB لسه محجوزة، لأن big مش بيتحرّر
console.log((process.memoryUsage().heapUsed / 1e6).toFixed(0), 'MB');

الـ trade-off هنا واضح: بتكسب حالة خاصة محفوظة ومحمية، بتخسر إن أي حاجة الـ closure ماسكها مش هتتحرر لحد ما الـ closure نفسه يتمسح. لو احتجت big.length بس، خزّن الرقم في متغير صغير وسيب المصفوفة تتحرر، بدل ما تمسك المليون عنصر كلهم.

متى لا تستخدم هذه الطريقة

الـ closures مش الحل في كل مكان. تجنّبها في الحالات دي: لما تكون بتولّد آلاف الـ closures في حلقة وكل واحد ماسك بيانات تقيلة، ده طريق مباشر لتسريب ذاكرة. ولما الحالة لازم تتشارك وتتعدّل من أماكن كتير ومحتاج تختبرها بسهولة، الـ closure بيخبّي الحالة فيصعّب الـ testing، وهنا class أو module بحقول واضحة أنضف. القاعدة: استخدم closure للحالة الخاصة الصغيرة المؤقتة، مش كبديل عن بنية بيانات حقيقية.

الخطوة التالية

افتح أقرب دالة setTimeout أو event handler في كودك ودوّر على متغير بيتقري جواها واتعرّف بره. ده closure شغّال عندك دلوقتي. اسأل نفسك: المتغير ده كبير؟ محتاج يفضل عايش كل العمر ده؟ لو لأ، فكّكه. وجرّب تعيد كتابة أي عدّاد عام عندك بـ makeCounter عشان تشيل المتغير العام.

المصادر

  • MDN Web Docs — Closures: developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
  • ECMAScript Language Specification — Lexical Environments & Environment Records: tc39.es/ecma262/#sec-environment-records
  • Node.js API — process.memoryUsage(): nodejs.org/api/process.html#processmemoryusage
  • MDN Web Docs — setTimeout: developer.mozilla.org/en-US/docs/Web/API/setTimeout

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة