در این مقاله میخوام در مورد توابع بازگشتی در جاوا اسکریپت صحبت کنم توابع بازگشتی توابعی هستند که میتونن خودشون رو فراخوانی کنند که چنین فرایندی به طور کلی «بازگشت» (recursion) نامیده میشود توابع بازگشتی ( recursion function ) در زبان های برنامه نویسی کاربرد های زیادی دارن و میتونن به ما کمک کنن تا از تکرار کد جلوگیری کنیم مثلا در بخش های از برنامه ما اگه نیاز باشه که روی یک فرایند یک کار تکراری انجام بدیم که این کار از یک الگوی مشترک پیروی میکنه میتونیم از توابع بازگشتی استفاده کنیم مثال معروف شو هم فک کنم همه بدونن و معمولا توی دانشگاه ها این تابع رو با این چند مثال توضیح میدن که میشه از اون برای حل سری فیبوناچی یا محاسبه فاکتوریل و … استفاده کرد.
اگر بخوام در یک تعریف ساده این اصطلاح رو بهتون توضیح بدم میشه گفت که اگر در تعریف بدنۀ یک فانکشن ، اون فانکشن بتونه خودش رو در داخل بدنه خودش فراخوانی کنه ، به همین سادگی ما یک فانکشن بازگشتی یا recursion نوشتیم
در این مثال قبل از نوشتن تابع بازگشتی من یک تابع ساده برای نمایش اعداد n تا ۰ می نویسم که در ابتدا با روش غیر بازگشتی حلش کردم بعدش اون رو تبدیل میکنم به یک تابع بازگشتی تا بتونید از این طریق مفهوم این نوع توابع رو کامل درک کنید.
خب در ابتدا من عدد ۵ رو به تابع countDown دادم که از طریق حلقه for عدد دریافتی رو از n تا ۰ نمایش بدیم فقط توجه کنید که منظور من ازn تا ۰ اینکه اگه یک عدد رو به این تابع بدیم مثلا ۵ این تابع در هر مرحله یکی از اون کم میکنه و نمایش میده تا اینکه به صفر برسه و اون عدد رو برای ما در کنسول لاگ چاپ میکنه به کد های زیر دقت کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // function countDown(n) { for (let i = n; i >= 0 ; i--) { console.log("result =>", i) } console.log("end count down") } countDown(5); //result => 5 //result => 4 //result => 3 //result => 2 //result => 1 //result => 0 //end count Down |
حالا مثال بالا رو با یک تابع بازگشتی انجام میدم براتون
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // function recursionCountDown(n){ if(n < 0){ console.log("end count Down") return; } console.log("recursion result =>", n); recursionCountDown(n - 1); } recursionCountDown(5) //recursion resul => 5 //recursion resul => 4 //recursion resul => 3 //recursion resul => 2 //recursion resul => 1 //recursion resul => 0 //end count Down |
اگر بخوام مثال بالا رو براتون توضیح بدم من تابع رو در بار اول فقط یک بار فراخوانی کردیم و عدد ۵ رو به اون پاس دادم و در دفعات بعد این تابع رو در داخل خودش فراخوانی کردم و هر دفعه از پارامتر ورودی اون یک مقداری رو کم کردم و از یک شرط اتمام فرآیند فراخوانی تابع استفاده کردم یعنی نکته مهم در توابع بازگشتی همین شرط اتمام فرایند فراخوانی است که باید به اون خیلی دقت کنید چون اگر این شرط نباشه تابع در یک حلقه بی نهایت می افته و برنامه با خطا مواجه می شه
اگر بخوام این مثال رو توضیح بدم و بگم ما در شرکت مون یک مگامنو داشتیم که هر دفعه داده هاشو از یک api دریافت میکردیم در یک قسمت نیاز بود که ما با استفاده از category id در هر سطحی که باشیم بیایم و parent category های این دسته بندی رو بدست بیارم پس اینجا بود که متوجه شدم باید حتما از یک تابع بازگشتی استفاده کنم چون بدون تابع بازگشتی نمیشه این مسئله رو حل کرد.
در این مثال یک آرایه داریم که داخل این آرایه تعداد object وجود داره که شامل مجموعه ای از دسته بندی هاست و در سه سطح می باشد اگه بخواهم بیشتر توضیح بدم ما میخوایم یک تابع بازگشتی بنویسیم که وقتی category id سطح سوم این لیست را به آن بدیم این تابع بازگشتی بیاد و متن این لیست و متن دو سطح بالای اون را هم پیدا کند و به ما بده.
فرض کنید که ما یک آرایه از دسته بندی منو های سایت مون داریم که میخوایم از طریق این آرایه منو ها و زیر منو های هر دسته بندی رو پیاده سازی کنیم من برای اینکه کاربرد توابع بازگشتی رو متوجه بشین میام و اول اون رو بدون اینکه از تابع بازگشتی استفاده کنم پیاده سازی میکنم و بعدش از طریق توابع بازگشتی همین مثال رو حل میکنم تا شما با مزایای توابع بازگشتی آشنا بشین و ببینید که چقدر از تکرار کد ها جلوگیری میکنه
فرض کنید که این ارایه همون دسته بندی های ماست که از طریق یک api دریافت کردیم و میخوایم اون رو نمایش بدیم و یا عملیات خاصی روش پیاده سازی کنیم
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | let array = [ { categoryId: 1, categoryText: "کالای دیجیتال", parentID: 0, }, { categoryId: 2, categoryText: "پوشاک", parentID: 0, }, { categoryId: 3, categoryText: "خانه و آشپز خانه", parentID: 0, }, { categoryId: 4, categoryText: "لباس مردانه", parentID: 2, }, { categoryId: 5, categoryText: "لباس زنانه", parentID: 2, }, { categoryId: 6, categoryText: "کفش مردانه", parentID: 4, }, { categoryId: 7, categoryText: "پیراهن و شلوار", parentID: 4, }, ]; |
حل کردن مسئله مطرح شده بالا بدون استفاده از توابع بازگشتی :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // function getTextCategory (id){ let text1,text2,text3; let categorytxtArray =[]; array.forEach((item1)=>{ if(item1.categoryId == id ){ text1 = item1.categoryText; categorytxtArray.push(text1) array.forEach((item2)=>{ if(item2.categoryId == item1.parentID){ text2 = item2.categoryText; categorytxtArray.push(text2) array.forEach((item3)=>{ if (item3.categoryId == item2.parentID) { text3 = item3.categoryText; categorytxtArray.push(text3) } }) } }) } }) return categorytxtArray; } console.log("get text category in Array =>",getTextCategory(7)); //get text category in Array => (3) ["پیراهن و شلوار", "لباس مردانه", "پوشاک"] * * |
همون طور که در بالا می بینید کدامون کمی پیچیده و تو در تو شده اگه بخوام بگم که توی این تابع چیکار کردم من تابع getTextCategory رو فراخونی کردم و ایدی سومین سطح یکی از دسته بندی ها رو بهش دادم که این تابع باید بیاد و متن یا نوشته هر دسته رو برای ما برگردونه که من از طریق forEach اومدم روی آرایه پیمایش انجام دادم و در هر مرحله مقدار ایدی رو که در آرگومان getTextCategory بهمون پاس داده شده گرفتم و با تمامی ایدی تمامی عناصر این ارایه مقایسه کردم بعدش اگه برابر بود با parent id یه بار دیگه forEach رو روی آرایه فراخوانی کردم و بعدش به همین صورت پیش رفتم تا به اول سطح برسم که parent id هاشون برابر صفر باشه
این مدل پیاده سازی کار رو کمی سخت و پیچیده میکنه که ما با استفاده از مزایای توابع بازگشتی میتونیم خیلی راحت همین کار رو انجام بدیم معایبی که داره اینکه اگر تعداد سطح دسته بندی ها بیشتر از سه زیر دسته باشه این تابع دیگه جواب گو کار نیست و باید یه سطح دیگه رو هم بهش بدیم تا بتونه پیمایش خودشو روی این دسته بندی انجام بده و اصلا پویا نیست
تابع بالا رو این بار با استفاده از تابع بازگشتی پیاده سازی کردم :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // const getCategoryText = (id) => { let newArray = []; function recersiveFunction(categoryId) { if (categoryId <= 0) { return newArray; } array.forEach((item) => { if (categoryId == item.categoryId) { newArray.push(item.categoryText); recersiveFunction(item.parentID) } }) } recersiveFunction(id); return newArray; } console.log("recersive end Array =>", getCategoryText(7)) //end Array &&& > (3) ["پیراهن و شلوار", "لباس مردانه", "پوشاک"] * * |
اگه بخوام در مورد کد های بالا توضیح بدم من اومد و یک تابع به اسم getCategoryText تعریف کردم و داخلش یک آرایه جدید تعریف کردم که بتونم در هر مرحله از اجرای تابع بازگشتی مقداری دسته بندی رو بگیره و داخل خودش ذخیره کنه و در اخر این مقدار رو برای ما return کنه
همون طوری که می بینید پیاده سازی منوی تو در تو از طریق توابع بازگشتی خیلی آسونه و اصلا پیچیده نیست و خوانایی کد رو بالا میبره ما با استفاده از تابع بازگشتی میتونیم در هر سطحی که باشیم خروجی مورد نظر مون رو دریافت کنیم یعنی اگه منوی ما n تا زیر منوی مختلف داشته باشه ما خیلی ساده از طریق تابع بازگشتی میتونیم دیتای مورد نظر مون رو دریافت کنیم
امیدوارم این مقاله برای شما مفید بوده باشه و تونسته باشم مفاهیم رو درست به شما انتقال بدم اگه سوال یا پیشنهادی داشتین میتونید از طریق بخش نظرات یا روش های که در قسمت تماس با من براتون گذاشتم نظرات و یا پیشنهادات خودتون رو با من در میون بزارین ممنونم که تا آخر این مقاله همراه من بودین