تشغّل الكود فينفجر محرك جافاسكريبت برسالة قصيرة حاسمة:
RangeError: Maximum call stack size exceeded
هذا هو «Stack Overflow» الحرفي — الخطأ الذي سُمي أشهر موقع برمجي في العالم على اسمه 😄. ومعناه شبه الدائم: دالة تستدعي نفسها إلى الأبد.
اقرأ الـ Stack Trace — التشخيص في ثوانٍ
وسّع الخطأ في الـ console وسترى المشهد المميز:
RangeError: Maximum call stack size exceeded
at countDown (app.js:2)
at countDown (app.js:3)
at countDown (app.js:3)
at countDown (app.js:3)
... (آلاف المرات)
نفس الاسم مكررًا بلا نهاية = هو المذنب. وإن رأيت اسمين يتناوبان
(a ثم b ثم a...) فهو تكرار متبادل بين دالتين.
الأسباب الأربعة الشائعة
| السبب | الشكل | الحل |
|---|---|---|
| recursion بلا حالة أساس | دالة عدّ تنسى شرط التوقف | أضف base case يعيد قيمة |
| حالة أساس لا تُبلغ أبدًا | العداد يبتعد عن الشرط بدل الاقتراب | تأكد أن كل استدعاء «يقترب» |
| استدعاء متبادل | a تستدعي b وb تستدعي a | اقطع الحلقة بشرط |
| getter/setter يستدعي نفسه | get name() { return this.name } | خزّن في اسم مختلف _name |
1) نسيان حالة الأساس — الكلاسيكية
// ❌ متى تتوقف؟ أبدًا
function countDown(n) {
console.log(n);
countDown(n - 1);
}
// ✅ حالة الأساس أولًا — باب الخروج
function countDown(n) {
if (n < 0) return; // شرط التوقف
console.log(n);
countDown(n - 1);
}
قاعدة كل recursion سليم: حالة أساس تعيد قيمة، وكل استدعاء يقترب منها.
2) الحالة موجودة... لكنها لا تُبلَغ
// ❌ الشرط n === 0 لكن نبدأ بـ 5.5 — سنقفز فوقه للأبد
function halve(n) {
if (n === 0) return;
halve(n - 1); // 5.5 → 4.5 → ... → 0.5 → -0.5 → ...
}
الحل: اجعل الشرط «شبكة» لا «نقطة»: if (n <= 0) return; — الشروط
المفتوحة (<=، >=) أأمن من المساواة الصارمة في التوقف.
3) getter يستدعي نفسه — الأخفى
// ❌ قراءة this.name داخل getter لـ name = استدعاء ذاتي أبدي
class User {
get name() { return this.name; }
set name(v) { this.name = v; } // نفس الكارثة
}
// ✅ التخزين باسم داخلي مختلف
class User {
get name() { return this._name; }
set name(v) { this._name = v; }
}
نفس الفخ يظهر في React: مكوّن يرندر نفسه داخل نفسه، أو setState يشغّل effect يعيد setState — قريبه المباشر.
4) نشر مصفوفة ضخمة (الحالة النادرة بلا recursion)
// ❌ كل عنصر يصبح وسيطًا في المكدس
Math.max(...millionNumbers);
// ✅ حلقة أو reduce
millionNumbers.reduce((m, x) => (x > m ? x : m), -Infinity);
قائمة تحقّق سريعة
- وسّع الـ stack trace: اسم متكرر؟ وجدت المذنب.
- لكل recursion: حالة أساس تعيد + كل استدعاء يقترب منها.
- شروط توقف بـ
<=/>=لا بمساواة صارمة قد تُقفز. - getters/setters: الاسم الداخلي
_nameغير اسم الخاصية. - عمق ضخم مشروع؟ حوّله لحلقة بمكدس يدوي.
الخلاصة
Maximum call stack size exceeded صرخة المحرك: «أحدهم يستدعي نفسه بلا
نهاية». افتح الـ trace، اقرأ الاسم المتكرر، وأصلح شرط التوقف — دقيقة
تشخيص تحل ما يبدو أعقد أخطاء جافاسكريبت.
أتقن الدوال من مسار جافاسكريبت بالعربي والتكرار من مسار هياكل البيانات، وأكمل مع حل حلقة useEffect اللانهائية و جدول أخطاء TypeError.