هذا المقال يتطلب مستوى مبتدئ — تحتاج فقط معرفة بسيطة بكتابة دوال 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) بمعلومات كل استدعاء دالة لسه ما خلصش.
الـ 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 قبل ما يقع:
// 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.