نکاتی در مورد ورودی های توابع
تعریف توابعی با تعداد ورودیهای متغیر
در بخش قبلی تابعی به نام mean تعریف کردیم که ۵ عدد را به عنوان ورودی دریافت کرده و میانگین آنها را محاسبه میکرد. حال فرض کنید قصد تعریف تابعی را داریم که همین کار را برای تعداد دلخواه ورودی انجام دهد.
پیش از این دیدیم که در جاوا اسکریپت میتوان توابع را با هر تعداد آرگومان ورودی فراخوانی کرد. و دیدیم که اگر تعداد آرگومانهای ورودی از تعداد پارامترهای موجود در تعریف تابع بیشتر باشد، از آرگومانهای اضافی صرف نظر میشود. همچنین اگر تعداد آرگومانهای ورودی از تعداد پارامترها کمتر باشد، مقدار پارامترهایی که هیچ آرگومانی برای آنها ارسال نشده، در بدنهی تابع برابر با undefined خواهد بود.
در جاوا اسکریپت دو روش برای حل مسئلهی فوق وجود دارد. روش اول استفاده از شِبه آرایهی arguments در بدنهی تابع و روش دوم استفاده از عملگر rest است که هر دو روش را بررسی میکنیم.
شبه آرایهی arguments
در بدنهی هر تابعی میتوان با استفاده از شبه آرایهی arguments به تمام آرگومانهای ارسال شده به آن تابع در زمان فراخوانی دسترسی پیدا کرد. یعنی بدون توجه به این که در تعریف تابع چند پارامتر ورودی برای آن تابع در نظر گرفته شده است. میتوان در هر فراخوانی با استفاده از شبه آرایهی arguments به آرگومانهای ارسال شده در همان فراخوانی دست یافت. arguments در واقع یک شئ (object) است. اما مانند آرایهها خاصیتی به نام length دارد که تعداد آرگومانهای ارسالی را مشخص میکند. همچنین مانند آرایهها میتوان با استفاده از اندیسهای عددی و نماد "[ ]" به عناصر آن دست یافت. اما متدهایی که در فصل سوم در مورد آرایهها معرفی کردیم (مانند join یا slice) بر روی این شئ قابل استفاده نیست. به همین دلیل به آن یک شبه آرایه گفته میشود.
حال میخواهیم همان تابع mean را طوری اصلاح کنیم که تعداد ورودیهای آن اختیاری باشد. یعنی هر تعداد عدد را به عنوان ورودی دریافت کرد. میانگین همان اعداد را محاسبه کرده و بازگرداند. تابع اصلاح شده به صورت زیر خواهد بود.
function mean(){
let average , total = 0;
for(let i = 0 ; i < arguments.length ; i++){
total += arguments[i];
}
average = total / arguments.length;
return average;
}
همانطور که مشاهده میکنید در این تابع یک حلقهی for ایجاد شده است که به تعداد عناصر موجود در آرایهی arguments تکرار میشود و در هر تکرار مقدار یک عنصر از آرایه را به مجموع مقادیر (total) اضافه میکند. در نهایت نیز مقدار مجموع کل آرگومانها بر تعداد آنها (length) تقسیم شده و میانگین به دست میآید که در خط بعدی این مقدار از تابع بازگردانده میشود. البته میتوان برای سادگی و کوتاه شدن کدها از ساختار تکرار for-of نیز به صورت زیر استفاده کرد.
function mean(){
let average , total = 0;
for(let number of arguments){
total += number;
}
average = total / arguments.length;
return average;
}
توجه کنید که در تعریف این تابع هیچ پارامتر ورودی برای این تابع در نظر گرفته نشده است. در این مثال خاص، قرار دادن هر تعداد پارامتر ورودی در تعریف تابع هیچ تغییری در عملکرد تابع ایجاد نخواهد کرد. زیرا در بدنهی تابع از هیچ پارامتر ورودی استفاده نشده است. حال میتوان تابع mean را با هر تعداد آرگومان ورودی دلخواه فراخوانی کرد و میانگین اعداد ورودی را محاسبه کرد. به نمونههای زیر توجه کنید.
mean(2);
← 2
mean(28 , 26);
← 27
mean(2 , 4 , 6 , 11 , 1);
← 4.8
برنامهی فوق را میتوانید اینجا در CodePen اجرا کنید. سعی کنید تعداد و مقدار ورودیها را تغییر داده و نتیجه را مشاهده کنید.
استفاده از عملگر rest
روش دوم برای ایجاد توابعی با تعداد متغیر ورودی، استفاده از عملگر rest است. نماد این عملگر کاملاً مشابه عملگر spread (یعنی سه نقطه) است. اما کاربردشان متفاوت است. برای ایجاد توابع با تعداد ورودیهای متغیر، کافی است قبل از نام پارامتر ورودی از عملگر rest استفاده کنیم. در این صورت تمام آرگومانهای ورودی به صورت یک آرایه در این پارامتر ورودی ذخیره میشوند. تابع mean را میتوان با استفاده از عملگر rest به صورت زیر اصلاح کرد.
function mean(...numbers){
let average , total = 0;
for(let number of numbers){
total += number;
}
average = total / numbers.length;
return average;
}
توجه کنید که در این روش، پارامتر ورودی (در این مثال پارامتر numbers) بر خلاف روش قبلی دقیقاً یک آرایه است و تمام متدهای مربوط به آرایهها را میتوان بر روی آن اجرا کرد. این مثال را نیز میتوانید اینجا اجرا کنید.
نکته : هر تابعی فقط میتواند یک پارامتر از نوع rest داشته باشد. در صورتی که تابعی علاوه بر این پارامتر rest دارای پارامترهای ثابت نیز باشد. در تعریف تابع، باید ابتدا پارامترهای ثابت را قرار دهیم و پارامتر rest همیشه باید در انتهای لیست پارامترها قرار گیرد.
مثلاً فرض کنید تابعی داریم که تعداد دلخواهی عدد را دریافت کرده و بر اساس مقدار یک پارامتر ورودی دیگر، تعیین میشود که چه عملی باید بر روی این اعداد انجام شود. قطعه کد زیر چنین تابعی را نشان میدهد. در صورتی که مقدار ورودی اول تابع "sum" باشد، تمام اعداد ورودی با هم جمع میشوند. و اگر مقدار ورودی اول "product" باشد، تمام اعداد در یکدیگر ضرب میشوند.
function sumOrProduct(operation , ...numbers){
let result;
if(operation == 'sum'){
result = 0;
for(let number of numbers){
result += number;
}
}else if(operation == 'product'){
result = 1;
for(let number of numbers){
result *= number;
}
}
return result;
}
حال برای فراخوانی تابع فوق باید در آرگومان اول نوع عملیات، و در آرگومانهای بعدی اعداد ورودی را مشخص کنیم. در قطعه کد زیر چند نمونه از فراخوانی این تابع را با ورودیهای متفاوت و خروجی متناظر آن مشاهده میکنید.
sumOrProduct('sum' , 4 , 6 , 11);
← 21
sumOrProduct('product' , 4 , 6 , 11);
← 264
sumOrProduct('sum' , 22 , 6 , 11 , 2.5);
← 41.5
این برنامه را نیز میتوانید اینجا اجرا کنید. باز هم تاکید میشود که در صورت وجود ورودیهای ثابت و متغیر (rest) در یک تابع، حتماً باید ورودیهای ثابت را در ابتدای لیست پارامترهای تابع قرار دهیم.
پارامترهای پیشفرض (Default Parameters)
در برخی مواقع لازم است تا برای بعضی از پارامترهای ورودی یک تابع، یک مقدار پیشفرض در نظر گرفته شود. یعنی اگر در زمان فراخوانی تابع، هیچ مقداری برای پارامتر مذکور ارسال نشود، از مقدار پیشفرض استفاده میشود. اما اگر مقداری برای اینگونه پارامترها ارسال شود، از همان مقدار ارسال شده در بدنهی تابع استفاده خواهد شد. به عنوان مثال به تابع زیر توجه کنید.
function writeMessage(count = 3){
for(let i = 0 ; i < count ; i++){
console.log("Hello World!");
}
}
این تابع یک عدد را دریافت میکند و پیام "Hello World!" را به تعداد آن عدد در کنسول چاپ میکند. توجه کنید مقدار پارامتر count در تعریف تابع برابر با ۳ در نظر گرفته شده است. یعنی اگر هیچ مقداری برای این پارامتر در نظر گرفته نشود، به صورت پیشفرض مقدار count برابر با ۳ خواهد. در نتیجه پیام "Hello World!" باید ۳ بار در کنسول چاپ شود. چند نمونه از اجرای تابع فوق با ورودیهای متفاوت را در قطعه کد زیر میتوانید مشاهده کنید. (این برنامه را میتوانید اینجا اجرا کنید)
writeMessage(2);
← Hello World!
← Hello World!
writeMessage(1);
← Hello World!
writeMessage();
← Hello World!
← Hello World!
← Hello World!
نکته : در صورتی که تابعی هم دارای پارامترهایی با مقدار پیشفرض و هم دارای پارامترهایی بدون مقدار پیشفرض باشد، پارامترهای دارای مقدار پیشفرض حتماً باید در انتهای لیست پارامترها قرار داده شوند.
به عنوان مثال تابع زیر مبلغ یک صورت حساب را به همراه میزان تخفیف دریافت میکند و مبلغ قابل پرداخت را در کنسول نمایش میدهد. اما میزان تخفیف اختیاری است و مقدار پیشفرض آن ۱۰ درصد است. در چنین شرایطی پارامتر مربوط به مبلغ که مقدار پیشفرضی ندارد حتماً باید قبل از پارامتر مربوط به تخفیف قرار بگیرد.
function discount(price , amount = 10){
console.log(price - (price * amount / 100));
}
حال برای فراخوانی این تابع میتوان به ترتیب مبلغ و درصد تخفیف را به تابع discount ارسال کرد. اما با توجه به اختیاری بودن پارامتر دوم، در صورتی که مقداری برای آن مشخص نشود. به صورت پیشفرض ۱۰ درصد تخفیف در نظر گرفته میشود. نمونههایی از اجرای این تابع را در قطعه کد زیر مشاهده میکنید.
discount(10000 , 10);
← 9000
discount(10000);
← 9000
discount(10000 , 20);
← 8000
این مثال را نیز میتوانید اینجا اجرا کنید. سعی کنید مقدار آرگومانها را تغییر داده و نتیجه را مشاهده کنید. همچنین میتوانید ترتیب پارامترهای ورودی تابع را تغییر دهید تا در عمل عدم عملکرد صحیح تابع را در این حالت مشاهده کنید.