العودية مفهوم قوي في البرمجة يقوم على استدعاء الدالة لنفسها. قد يكون فهمها صعبًا بعض الشيء في البداية، لكنك متى أدركت أساسياتها أصبحت أداة قيّمة لحل المسائل المعقدة. في هذا الدرس، سنستكشف العودية في JavaScript من خلال أمثلة سهلة الفهم.
تحدث العودية عندما تستدعي الدالة نفسها، سواء بشكل مباشر أو غير مباشر. وهي تشبه الحلقة، لكنها تقوم على تقسيم المسألة إلى مسائل فرعية أصغر وأسهل في التعامل معها.
لنبدأ بمثال بسيط: دالة العد التنازلي.
function countdown(num) {
// Base case
if (num <= 0) {
console.log('Blastoff!');
return;
}
// Recursive case
console.log(num);
countdown(num - 1);
}
// Call the function
countdown(5);
في هذا المثال:
num أصغر من أو تساوي 0، تطبع الدالة "Blastoff!" وتتوقف عن استدعاء نفسها.num الحالية وتستدعي نفسها بالقيمة num - 1.الآن، لنلقِ نظرة على مثال كلاسيكي على العودية: حساب مضروب عدد.
function factorial(n) {
// Base case
if (n === 0 || n === 1) {
return 1;
}
// Recursive case
return n * factorial(n - 1);
}
// Test the function
console.log(factorial(5)); // Output: 120
في هذا المثال:
n تساوي 0 أو 1، تُرجع الدالة 1.n في مضروب n - 1.يجب أن تحتوي كل دالة عودية على حالة أساسية واحدة على الأقل، وهي شرط تتوقف عنده الدالة عن استدعاء نفسها. وبدون حالة أساسية، ستستمر العودية بلا نهاية، مما يؤدي إلى تجاوز سعة المكدس.
تحدد الحالة العودية كيفية استدعاء الدالة لنفسها بنسخة أصغر أو أبسط من المسألة.
المزايا:
العيوب:
العودية تقنية قيّمة يمكنها تبسيط المسائل المعقدة عبر تقسيمها إلى مسائل فرعية أصغر وأسهل في التعامل معها. وفهم الحالات الأساسية والحالات العودية أمر بالغ الأهمية لتنفيذ حلول عودية فعّالة في JavaScript.
تعلّم المزيد:
أنت تدير متجر بيتزا، وتقدّم ثلاثة أنواع من البيتزا:
إذا أراد العملاء، يمكنهم إضافة عدد غير محدود من الخيارات الإضافية: إما ExtraSauce مقابل $1 أو ExtraToppings مقابل $2.
مهمتك هي كتابة كود يساعد العميل في معرفة التكلفة التي سيدفعها.
عند تمرير اسم البيتزا كأول وسيط، مع عدد غير محدود من الخيارات المضافة، احسب سعر البيتزا بالدولارات.
pizzaPrice('Margherita');
// => 7
pizzaPrice('Caprese', 'ExtraSauce', 'ExtraToppings');
// => 12
pizzaPrice(
'Caprese',
'ExtraToppings',
'ExtraToppings',
'ExtraToppings',
'ExtraToppings',
);
// => 17
تُستدعى دالتك بقائمة من كائنات PizzaOrder، ويجب أن تُرجع السعر الإجمالي للطلب بالدولارات.
كل PizzaOrder له خاصية pizza وهي اسم البيتزا، وخاصية extras وهي قائمة الخيارات الإضافية.
const margherita = new PizzaOrder('Margherita');
const caprese = new PizzaOrder('Caprese', 'ExtraToppings');
orderPrice([margherita, caprese]);
// => 18
ستدرك أنك لا تستطيع كتابة هذه الدالة باستخدام الاستدعاء الذاتي، لأن اختبارًا واحدًا بعدد هائل من الطلبات سيرفع الخطأ Maximum call stack size exceeded.
لا تقلق، هذا مقصود. جرّب تنفيذ هذه الدالة باستخدام حلقة أمرية!
لديك خيارات كثيرة، منها على سبيل المثال لا الحصر استخدام reduce أو حلقة for.
عندما يشغّل مفسّر JavaScript كود JavaScript، فإنه يتتبّع الدوال التي دخلها (التي بدأ استدعاءها) في بنية بيانات تُسمى "المكدّس". وعندما تُرجع الدالة (تنتهي)، تُزال من المكدّس.
لكن حجم هذا المكدّس محدود. الخطأ الأكثر شيوعًا هو دالة تكرّر استدعاء نفسها ولا تنتهي أبدًا. يُوضع كل استدعاء على المكدّس، وقبل أن يُرجع، يُوضع استدعاء آخر على المكدّس.
function kaboom() {
kaboom()
}
kaboom()
// => RangeError: Maximum call stack size exceeded
يُظهر تتبّع المكدّس لهذا الخطأ السطر نفسه مرارًا وتكرارًا، وهذا منطقي لأن الدالة تستدعي نفسها. ورغم أنه لا فائدة عملية حقيقية له في معظم الحالات، يمكنك معرفة إلى أي ارتفاع يمكن أن يصل ذلك المكدّس.
let calls = 0;
function kaboom() {
calls +=1 ;
kaboom()
}
kaboom()
// => RangeError: Maximum call stack size exceeded
console.log(calls)
// => a number, generally higher than 10.000
لا يوجد سوى حلّين عمليين لخطأ المكدّس الناتج عن دالة تكرّر استدعاء نفسها بشكل متزامن:
سجّل في Exercism لتتعلّم وتتقن JavaScript عبر 37 مفهومًا159 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.