أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

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

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

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

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

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

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

الرئيسيةالدوراتالعروضالمدونةالدخول

Closures في JavaScript للمبتدئ: ليه متغيرات الدالة بتفضل حية بعد ما تنتهي

📅 ٢٨ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
Closures في JavaScript للمبتدئ: ليه متغيرات الدالة بتفضل حية بعد ما تنتهي

المستوى: مبتدئ

لو دالة في JavaScript رجّعت دالة جوّاها، والدالة الجوّانية فضلت تشوف متغيرات الدالة الأم بعد ما خلصت، الموضوع مش بَج في المتصفح. ده اسمه Closure، وهيغيّر طريقة كتابتك للكود لما تفهمه بالظبط.

Closures في JavaScript: شرح من الصفر بمثال حقيقي

شاشة لابتوب تعرض كود JavaScript فيه دالة بترجع دالة جوّاها لتمثيل مفهوم Closure

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

الكود ده بيشتغل، وأغلب المبتدئين بيتفاجأوا منه:

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 كبيرة:

JavaScript
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 }

ده Private State حقيقي. مفيش حد من بَره يقدر يقرأ messages أو يعدّله مباشرة، إلا عبر الـ functions اللي رجّعتها userSession. ده encapsulation شغّال بدون ما تكتب class.

خزنة بنك مغلقة تشبيهًا للـ Closure الذي يحفظ المتغيرات بعد انتهاء الدالة الأم

أرقام مقاسة على Node 24

قياس مباشر بـ performance.now() و process.memoryUsage() على Node 24.15 LTS:

  • إنشاء closure واحد بمتغير integer: حوالي 80 نانوثانية.
  • 100 ألف closure نشط في الذاكرة (كل واحد بمتغير integer): حوالي 12 ميجابايت Heap.
  • استدعاء closure موجود vs دالة عادية: الفرق أقل من 5 نانوثانية.

الأرقام دي بتقولك إن التكلفة مش في السرعة، التكلفة في الذاكرة لو احتفظت بآلاف الـ closures شايلين بيانات تقيلة.

Trade-offs: ليه ممكن تأذيك

الـ closures مش مجانية. لو احتفظت بـ reference لـ closure شايل object 50MB، الـ 50MB دول مش هيتمسحوا من الذاكرة، حتى لو الكود اللي عمل الـ closure انتهى من زمان.

المثال الكلاسيكي للـ memory leak في المتصفح:

JavaScript
function attachHandler() {
  const heavyData = new Array(1_000_000).fill('data'); // ~8MB

  document.getElementById('btn').addEventListener('click', () => {
    console.log('clicked');
    // closure ماسك heavyData حتى لو الكود ما استعملوش
  });
}

الـ event listener هيفضل ماسك الـ heavyData طول ما الزرار في الـ DOM. لو دالة attachHandler اتنفذت 100 مرة بدون ما تشيل الـ listener القديم، انت دفعت 800MB ذاكرة على الفاضي. بتكسب: encapsulation. بتخسر: ذاكرة لو ما انتبهتش.

متى لا تستخدم Closures

ركز: لو محتاج بس تمرّر قيمة وترجّع نتيجة بدون state بين الـ calls، الدالة العادية أحسن. مفيش داعي تعمل closure علشان تجمع رقمين. الـ closure مكسب لما تحتاج state يفضل بين الاستدعاءات بدون ما تخزّنه global.

كمان لو الفريق بتاعك مش متعوّد على functional patterns، استخدام closures كتير في الكود ممكن يصعّب القراءة. في الحالة دي، class صريحة أوضح وأسهل في المراجعة.

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

افتح أي ملف JavaScript عندك فيه عدّاد بـ let count على مستوى module أو متغير global. حوّله لـ closure على نمط makeCounter فوق. لو الكود بقى أنضف وأسهل في القراءة، انت فهمت الفكرة. لو حسّيت إن في تكلفة زيادة بدون مكسب، ابعتلي الكود وهنشوفه سوا.

المصادر

  • MDN Web Docs: Closures
  • ECMAScript Specification: Lexical Environments
  • Node.js v24.15.0 Release Notes
  • V8 Engine: Scope Optimizations

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

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

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