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

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

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

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

المنصة

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

الدعم

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

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

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

Recursion للمبتدئ: ليه الدالة بتنادي نفسها وإمتى تتجنّبها

📅 ١٤ مايو ٢٠٢٦⏱ 6 دقائق قراءة
Recursion للمبتدئ: ليه الدالة بتنادي نفسها وإمتى تتجنّبها

الاستدعاء الذاتي (Recursion): لما الدالة تحلّ المشكلة بإنها تنادي نفسها

المستوى: مبتدئ. المقال ده مناسب لو انت لسه في بداية طريقك في البرمجة، سمعت كلمة Recursion كذا مرة، وكل ما تحاول تفهمها دماغك بتلفّ في حلقة. مش هتحتاج غير إنك تعرف تكتب دالة بسيطة وتنادي عليها.

بعد المقال ده هتقدر تقرا أي دالة recursive وتعرف بالظبط بتعمل إيه، وهتعرف تكتب واحدة بنفسك من غير ما تقع في الحلقة اللا نهائية اللي بتوقّع البرنامج. الاستدعاء الذاتي مش حيلة ذكية للاستعراض — هو أوضح طريقة لحل نوع معيّن من المشاكل، وكارثة لو استخدمته في النوع الغلط.

الفكرة الأول في مثال — قبل أي تعريف

تخيّل إنك واقف في طابور طويل، وعايز تعرف انت رقم كام، بس مش شايف أوله. أبسط حل: تسأل اللي قدامك "انت رقم كام؟". هو برضه مش عارف، فيسأل اللي قدامه نفس السؤال، وهكذا.

السؤال بيتنقل لقدّام لحد ما يوصل لأول واحد في الطابور. ده الوحيد اللي بيرد على طول من غير ما يسأل حد: "أنا رقم 1". الرد ده بيرجع للي وراه، يزوّد عليه واحد ويبقى 2، يرجّعه للي وراه يبقى 3، لحد ما الرقم يوصلك انت.

اللي حصل ده بالظبط هو الـ Recursion: كل واحد حلّ نسخة أصغر من نفس المشكلة، واعتمد على إن في "حالة بسيطة" في الآخر بترد من غير ما تسأل تاني. ركّز في حاجتين: في خطوة بتصغّر المشكلة (تسأل اللي قدامك)، وفي نقطة توقف (أول واحد في الطابور). الاتنين دول هما كل الحكاية.

تعريف الـ Recursion بشكل دقيق

الـ Recursion (الاستدعاء الذاتي) هو أسلوب الدالة فيه بتستدعي نفسها لحل نسخة أصغر من نفس المشكلة، لحد ما توصل لحالة بسيطة جدًا تقدر تحلها فورًا من غير استدعاء جديد.

الحالة البسيطة دي اسمها base case (حالة التوقف)، والجزء اللي بيستدعي نفسه على نسخة أصغر اسمه recursive case (الحالة التكرارية). أي دالة recursive من غير base case واضح هتفضل تنادي نفسها للأبد، لحد ما الذاكرة تخلص ويقع البرنامج.

أي دالة recursive لازم فيها حاجتين

خد المثال الكلاسيكي: مضروب العدد (factorial). مضروب 5 يعني 5 × 4 × 3 × 2 × 1. لاحظ إن مضروب 5 = 5 × مضروب 4. ده تعريف recursive جاهز.

JavaScript
function factorial(n) {
  if (n <= 1) return 1;         // base case: نقطة التوقف
  return n * factorial(n - 1);   // recursive case: نسخة أصغر
}

console.log(factorial(5)); // 120

السطر الأول هو حالة التوقف: لو n وصلت 1، رجّع 1 على طول وبلاش استدعاء جديد. السطر التاني بيصغّر المشكلة بـ factorial(n - 1). الترتيب مهم — لو نسيت الـ base case، الدالة مش هتعرف تقف خالص.

إيه اللي بيحصل في الذاكرة فعليًا: الـ Call Stack

تخيّل رصّة أطباق. كل ما تحتاج طبق جديد، بتحطّه فوق الرصّة. وأول ما تخلّص، بتشيل الطبق اللي فوق خالص الأول، مش اللي تحت. مينفعش توصل للي تحت قبل ما تشيل اللي فوقه.

ده بالظبط الـ Call Stack (مكدّس الاستدعاءات): مكان في الذاكرة بيتسجّل فيه كل استدعاء دالة شغّال دلوقتي. لما factorial(5) ينادي factorial(4)، الأول بيستنى على المكدّس لحد ما التاني يخلّص. الاستدعاءات بتتراصّ لحد ما توصل للـ base case، وبعدين بترجع القيم واحدة واحدة من فوق لتحت.

المكدّس ده مساحته محدودة. في Node.js 22 بإعداداته الافتراضية، لو الدالة نادت نفسها أعمق من حوالي 10 لـ 15 ألف مستوى متداخل (الرقم بيختلف حسب حجم كل frame)، البرنامج بيرمي الخطأ RangeError: Maximum call stack size exceeded ويقف. ده أهم فرق عملي بين الـ recursion والـ loop العادي: الـ loop مبيستهلكش مكدّس.

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

مثلث سيربينسكي اللي فوق ده مرسوم بـ recursion: ارسم مثلث، وجوّاه ارسم 3 مثلثات أصغر بنفس القاعدة، وكرّر. كل مثلث صغير هو نسخة مصغّرة من الكبير — وده اسمه التشابه الذاتي، وهو السبب اللي بيخلّي الـ recursion طبيعي جدًا في المشاكل الشجرية.

مثال واقعي: المرور على شجرة التعليقات

لو عندك نظام تعليقات زي اللي في فيسبوك: كل تعليق ممكن يكون تحته ردود، وكل رد ممكن يكون تحته ردود تانية، لأي عمق. لو عايز تعدّ كل التعليقات في موضوع فيه 4,000 تعليق متداخلين، الـ loop العادي مش هينفع لأنك مش عارف العمق مقدمًا.

JavaScript
function countComments(comment) {
  let total = 1; // التعليق نفسه
  for (const reply of comment.replies) {
    total += countComments(reply); // عُدّ كل رد، وردود كل رد
  }
  return total;
}

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

الـ Trade-offs اللي لازم تعرفها

  • بتكسب وضوح، بتخسر ذاكرة. الكود الـ recursive بيوصف المشكلة الشجرية في سطور قليلة، لكن كل استدعاء بياخد frame على المكدّس. كل ما العمق زاد، استهلاك الذاكرة زاد.
  • بتكسب اختصار، بتخسر سهولة التتبّع. لما يحصل bug، الـ stack trace بيبقى طويل ومكرر، وتتبّعه أصعب من loop واضح.
  • أبطأ شوية من الـ loop. كل استدعاء دالة له تكلفة (overhead). في الأعماق الصغيرة الفرق مش محسوس، لكنه بيتراكم في الكود اللي بيتنفّذ ملايين المرات.
  • خطر الـ stack overflow. JavaScript في معظم محرّكاتها مبتفعّلش الـ tail-call optimization، يعني الأعماق الكبيرة بترمي خطأ بدل ما تشتغل.

الافتراض هنا إن الأمثلة والأرقام مبنية على JavaScript و Node.js 22. المفهوم نفسه واحد في أي لغة، لكن حدود المكدّس وسلوك tail-call بيختلفوا من لغة للتانية.

متى لا تستخدم الـ Recursion

لو المشكلة خطية بسيطة — مجرد المرور على مصفوفة عنصر عنصر — الـ loop العادي أوضح وأأمن، ومفيش أي سبب تستخدم recursion. ولو العمق ممكن يعدّي آلاف المستويات (زي معالجة ملف ضخم سطر بسطر بشكل recursive)، استخدم loop مع stack صريح بدل ما تعتمد على مكدّس البرنامج. القاعدة البسيطة: الـ recursion للبيانات الشجرية، والـ loop لأي حاجة تانية.

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

افتح أي كود عندك فيه loops متداخلة بتمشي على بيانات شجرية — folders جوّا folders، أو categories، أو تعليقات. جرّب تكتب نسخة recursive منها وقارن وضوح الكود. وفي أي دالة recursive بتكتبها، اكتب الـ base case الأول قبل أي سطر تاني — لو لقيت نفسك محتاج أكتر من حالة توقف، ده غالبًا معناه إن المشكلة مش شجرية أصلًا، والـ loop هو الحل الصح.

المصادر

  • MDN Web Docs — توثيق الدوال والاستدعاء الذاتي (Functions / Recursion).
  • ECMA-262, The ECMAScript Language Specification — قسم execution contexts والـ stack.
  • Abelson & Sussman, "Structure and Interpretation of Computer Programs" (SICP) — الفصل الأول: الفرق بين العمليات الـ recursive والـ iterative.
  • Cormen, Leiserson, Rivest, Stein, "Introduction to Algorithms" (CLRS) — فصل الـ recurrences وتحليل الدوال الـ recursive.
  • توثيق Node.js الرسمي — خيار --stack-size وحدود الـ call stack في محرّك V8.

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

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

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