«بازگشت» مفهومی قدرتمند در برنامهنویسی است که در آن یک تابع خودش را فراخوانی میکند. شاید در ابتدا کمی سخت به نظر برسد، اما وقتی اصول پایه را بفهمید، به ابزاری ارزشمند برای حل مسائل پیچیده تبدیل میشود. در این آموزش، بازگشت را در 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 کوچکتر یا مساوی ۰ میشود، تابع «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 برابر ۰ یا ۱ باشد، تابع ۱ را برمیگرداند.n را در فاکتوریل n - 1 ضرب میکند.هر تابع بازگشتی باید حداقل یک حالت پایه داشته باشد؛ شرطی که در آن تابع فراخوانی خود را متوقف میکند. بدون حالت پایه، بازگشت بیپایان ادامه مییابد و به سرریز پشته میانجامد.
حالت بازگشتی مشخص میکند که تابع چگونه خودش را با نسخهی کوچکتر یا سادهتر مسئله فراخوانی میکند.
مزایا:
معایب:
بازگشت تکنیکی ارزشمند است که میتواند مسائل پیچیده را با شکستن آنها به زیرمسئلههای کوچکتر و قابلمدیریتتر ساده کند. درک حالتهای پایه و حالتهای بازگشتی برای پیادهسازی راهحلهای بازگشتی مؤثر در JavaScript ضروری است.
بیشتر بدانید:
شما یک پیتزافروشی دارید و سه نوع پیتزا عرضه میکنید:
اگر مشتریها بخواهند، میتوانند به تعداد نامحدود گزینهی اضافه سفارش دهند: یا «ExtraSauce» به قیمت ۱ دلار یا «ExtraToppings» به قیمت ۲ دلار.
وظیفهی شما نوشتن کدی است که به مشتری کمک میکند هزینهاش را حساب کند.
با دریافت اسم پیتزا به عنوان آرگومان اول و هر تعداد گزینهی اضافه، قیمت پیتزا را بر حسب دلار محاسبه کنید.
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 تمرین و مربیگری انسانی واقعی یاد بگیرید و در آن استاد شوید، همهی اینها رایگان.