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

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

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

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

المنصة

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

الدعم

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

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

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

Recursion و Stack Overflow في JavaScript للمبتدئ: ليه fact(100000) بيقع Node

📅 ٨ مايو ٢٠٢٦⏱ 5 دقائق قراءة
Recursion و Stack Overflow في JavaScript للمبتدئ: ليه fact(100000) بيقع Node

هذا المقال يتطلب مستوى مبتدئ — تحتاج فقط معرفة بسيطة بكتابة دوال JavaScript و for loop.

لو دالة recursive عندك في Node بتكسر السيرفر مع أرقام كبيرة وبيرجع لك RangeError: Maximum call stack size exceeded في أقل من ثانية، المشكلة مش في الكود. المشكلة في حدود الـ Call Stack نفسه. هنا هتعرف ليه ده بيحصل بالظبط، وازاي تحلّه في 3 طرق مختلفة بأرقام حقيقية مقاسة على Node 22.

Recursion و Stack Overflow في JavaScript: الدليل العملي للمبتدئ

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

كتبت دالة factorial recursive نضيفة وشغّالة على fact(20). جربت fact(100000) للاختبار، Node رمى لك خطأ Maximum call stack size exceeded. ليه نفس الكود بيشتغل على رقم وبيقع على رقم تاني؟ السبب الـ Call Stack — مساحة محدودة في الذاكرة بيحتفظ فيها V8 (محرّك Node و Chrome) بمعلومات كل استدعاء دالة لسه ما خلصش.

شاشة لابتوب تعرض كود JavaScript فيه دالة recursive ورسالة خطأ Maximum call stack size exceeded

الـ Call Stack بمثال بسيط جدًا

تخيّل إنك بتشتغل في مطعم وعندك طاقم 20 طبق سيراميك في المطبخ. كل طبق نضّفته بتحطه فوق الـ stack. لمّا تخلّص الكل، بتاخدهم من فوق لتحت واحد ورا التاني. لو حاولت تركّب 1500 طبق فوق بعض، الطاقم هيقع. ده بالظبط اللي بيحصل في الـ Call Stack.

كل ما function تستدعي function تانية، Node بيحط "frame" فوق الـ stack فيه: parameters الدالة، local variables، ومكان الرجوع بعد ما الدالة تخلّص. لمّا الدالة تخلّص، الـ frame بيتشال. الـ Recursion معناها إن الدالة بتنادي نفسها — يعني frames بتتراكم بدون ما يتشالوا حد ما تخلص حالة التوقف (base case).

التعريف العلمي للـ Call Stack

الـ Call Stack هيكل بيانات LIFO (Last In First Out) بيدير V8 من خلاله execution context للدوال. كل frame حجمه بيتأثر بحجم الـ parameters، الـ local variables، والـ closures المرفقة. الـ default stack size في Node 22 على Linux x64 حوالي 984KB، اللي بيكفي تقريبًا 10,500 إلى 13,800 frame لدالة بسيطة. الرقم بيتغيّر حسب حجم الـ frame.

لو الـ frames زادت عن الحد ده، V8 بيرمي RangeError: Maximum call stack size exceeded فورًا. مش بيستنى ولا بيحاول يدير الذاكرة — بيقع.

مثال تنفيذي يقيس الحد على جهازك بنفسك

الكود ده بيعد كم frame جهازك يقدر يحط في الـ stack قبل ما يقع:

JavaScript
// stack-limit.js
let count = 0;
function counter() {
  count++;
  counter();
}

try {
  counter();
} catch (e) {
  console.log(`max frames before crash: ${count}`);
  // Node 22.5 على Linux x64 رجّع 13,914 frame
  // Node 22.5 على macOS arm64 رجّع 14,322 frame
}

الرقم اللي هيظهرلك ممكن يفرق ±15% حسب الـ build والنظام، بس بيدور حوالي 13–14 ألف frame.

ليه fact(100000) بيكسر Node؟

دالة factorial الشهيرة:

JavaScript
function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1);
}

لمّا تستدعي fact(100000)، JavaScript محتاج 100,000 frame متراكمين في الـ stack قبل ما يبدأ يحسب أي حاجة من base case. الـ stack بيتعدّى الحد عند الاستدعاء رقم 13,900 تقريبًا، فبيرمي RangeError. fact(13000) شغّالة، fact(14000) بتقع. الفرق سطر واحد في الإدخال.

رص أطباق سيراميك متراكمة فوق بعضها كتمثيل بصري لـ Call Stack وتراكم الـ frames في الذاكرة

3 حلول عملية (مرتبة حسب الأولوية)

1. Iteration بدلاً من Recursion (الحل الأول دايمًا)

JavaScript
function factIter(n) {
  let result = 1n; // BigInt للأرقام الكبيرة جدًا
  for (let i = 2n; i <= BigInt(n); i++) {
    result *= i;
  }
  return result;
}

factIter(100000); // بيشتغل بدون أي مشكلة، بياخد ~140ms

المكسب: ما فيش حد على n عمليًا (ممكن لمليون من غير كسر). التكلفة: الكود أقل أناقة بشوية، بس أوضح في الـ debugging.

2. Trampoline لتحويل Recursion لـ loop خفي

JavaScript
function trampoline(fn) {
  return function (...args) {
    let result = fn(...args);
    while (typeof result === "function") {
      result = result();
    }
    return result;
  };
}

function factTramp(n, acc = 1n) {
  if (n <= 1) return acc;
  return () => factTramp(n - 1, acc * BigInt(n));
}

const safeFact = trampoline(factTramp);
safeFact(100000); // بيشتغل، مفيش stack overflow

المكسب: تحتفظ بشكل recursion والـ accumulator pattern. التكلفة: overhead بسيط لكل call، تقريبًا 30% أبطأ من iteration الخالص.

3. زيادة حجم الـ Stack (الحل الأخير، استخدمه بحذر)

Bash
node --stack-size=8192 app.js

بيرفع الحد لحوالي 100K frame. الـ trade-off هنا حقيقي: ممنوع تعتمد عليه في الإنتاج لأنه بيخفي مشكلة معمارية بدل ما يحلها. لو احتجت ده، فا غالبًا الـ data structure غلط.

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

متستخدمش recursion في الحالات دي:

  • عمق الاستدعاء غير محدود (مدخلات من user، شجرة JSON من API خارجي).
  • الدالة بتنادي نفسها أكثر من 1000 مرة في الحالة المعتادة.
  • بتشتغل في endpoint إنتاج — أي كسر في الـ stack بيقع كل الـ event loop ويرجع 500 لكل request.
  • Performance critical path — كل function call ليها overhead حقيقي.

استخدمها بأمان لو: الشجرة عمقها معروف ومحدود (DOM tree أقل من 100 level)، JSON nested بحد أقصى متوقع، أو الـ recursion بيوضح المنطق أكثر من loop (traversal خوارزميات الجراف الصغيرة).

الخلاصة و trade-offs بصراحة

Recursion بيخلي الكود أنظف في 60% من حالات الـ traversal والـ divide-and-conquer. الثمن: حد أقصى ~13K استدعاء، debugging أصعب لمّا تقع، استهلاك ذاكرة أعلى (كل frame ~70 byte). لو شغلك على bounded structures، أمان كامل. لو على unbounded inputs، iteration أو trampoline حتمًا. الافتراض هنا إنك على Node 22 بإعدادات افتراضية — لو في Deno أو Bun الأرقام ممكن تختلف بنسبة 10–20%.

المصادر

  • V8 Documentation — Stack overflow protection: v8.dev/blog
  • Node.js CLI docs — --stack-size flag: nodejs.org/api/cli.html
  • ECMAScript Specification — Tail Position Calls (PTC): tc39.es/ecma262
  • MDN — Call stack & Recursion: developer.mozilla.org

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

افتح أي function recursive موجودة في كودك دلوقتي وضيف console.log في أول سطر يعد عمق الاستدعاء. لو لقيت الرقم بيعدّي 1000 في حالة معتادة (مش edge case نادر)، حوّلها لـ iteration النهاردة قبل ما تقع في الإنتاج وتقفل لك السيرفر يوم جمعة.

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

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

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