در این درس میخوانید
وقتی فرمول یا یک قاعده در چند جای برنامه تکرار میشود، تغییر آن پرخطا خواهد شد. تابع بخشی از منطق را در یک واحد نامدار قرار میدهد. یک تابع خوب لازم نیست کوتاهترین کد ممکن باشد؛ باید کار مشخصی داشته باشد و بتوان ورودی و نتیجهٔ آن را توضیح داد. در درس ششم، تابع محاسبهٔ قیمت مینویسیم و سپس میبینیم چگونه یک تابع میتواند تابع دیگری بسازد. مثالها در Console تازه یا فایل JavaScript مستقل اجرا میشوند.
پارامتر، آرگومان و مقدار بازگشتی
پارامتر نامی است که هنگام تعریف تابع مینویسیم؛ آرگومان مقدار واقعی هنگام فراخوانی است. return مقدار را به محل فراخوانی برمیگرداند و اجرای همان تابع را پایان میدهد. اگر return ننویسید، نتیجهٔ معمول تابع undefined است. نمایش نتیجه با console.log کار دیگری است: چیزی را چاپ میکند و جای قرارداد خروجی تابع را نمیگیرد. برای قابل استفادهبودن در صفحه، آزمون و محاسبهٔ بعدی، مقدار را برگردانید.
function calculateTotal(unitPrice, quantity) {
return unitPrice * quantity;
}
const total = calculateTotal(15000, 3);
console.log(total); // 45000
console.log(calculateTotal(15000, 1) + 5000); // 20000قرارداد ورودی و مقدار پیشفرض
در مثال اول فرض کردهایم دو ورودی معتبرند. در برنامهٔ واقعی باید مشخص باشد تابع عدد میگیرد یا خودش مسئول تبدیل متن است. مخلوطکردن این دو مسئولیت میتواند دادهٔ نامعتبر را بیصدا عبور دهد. پارامتر پیشفرض زمانی اعمال میشود که آرگومان داده نشده یا undefined باشد؛ null همان نبود آرگومان نیست. از نام دقیق استفاده کنید: اگر مقدار بر حسب تومان است، این واحد را در نام یا قرارداد تابع روشن کنید.
function greeting(name = "یادگیرنده") {
return `سلام ${name}`;
}
console.log(greeting()); // سلام یادگیرنده
console.log(greeting("سارا")); // سلام سارا
console.log(greeting(null)); // سلام nullتابع پیکانی و نقش تابع بهعنوان مقدار
تابع در JavaScript میتواند داخل متغیر ذخیره شود، به تابع دیگر داده شود یا از آن برگردد. Arrow Function برای بعضی تابعهای کوتاه مفید است. در شکل بدون آکولاد، نتیجهٔ عبارت بهصورت ضمنی برمیگردد؛ با آکولاد باید return را خودتان بنویسید. تابع پیکانی this مخصوص خودش را نمیسازد، بنابراین جایگزین مکانیکی همهٔ متدها و constructorها نیست. این موضوع را در درس this و کلاسها دوباره بررسی میکنیم.
const double = value => value * 2;
const triple = value => {
return value * 3;
};
function applyOperation(value, operation) {
return operation(value);
}
console.log(applyOperation(4, double)); // 8
console.log(triple(4)); // 12Scope؛ یک نام از کجا پیدا میشود؟
تابع به متغیرهای دامنهٔ بیرونیِ محل تعریفش دسترسی دارد. نامی که داخل تابع تعریف شده، بهصورت معمول از بیرون قابل دسترسی نیست. به این قاعدهٔ وابسته به محل تعریف، دامنهٔ واژگانی یا lexical scope میگوییم. برای تابع محاسباتی، گرفتن داده از پارامتر معمولاً وابستگی را روشنتر از خواندن چند متغیر سراسری میکند. وقتی نتیجه فقط به ورودیها وابسته باشد و دادهٔ بیرونی را تغییر ندهد، آزمون آن نیز آسانتر میشود.
const taxRate = 0.1;
function addTax(amount) {
const tax = amount * taxRate;
return amount + tax;
}
console.log(addTax(100)); // 110
// console.log(tax); // tax بیرون تابع تعریف نشده است.Closure با یک سناریوی واقعی
Closure ترکیب تابع با دسترسی آن به محیط واژگانی پیرامونش است. این دسترسی پس از پایان فراخوانی تابع بیرونی هم میتواند باقی بماند. در نمونهٔ زیر، برای هر نرخ یک تابع تخفیف مستقل میسازیم. نرخ از بیرون به تابع سازنده میرسد و تابع بازگشتی آن را برای محاسبههای بعدی در دسترس دارد. این رفتار برای ساخت تنظیمات ثابتِ یک محاسبه مفید است، اما نباید راهی برای پنهانکردن وابستگیهای پیچیده باشد.
function makeDiscount(rate) {
return function (price) {
return price * (1 - rate);
};
}
const studentPrice = makeDiscount(0.2);
const festivalPrice = makeDiscount(0.1);
console.log(studentPrice(100000)); // 80000
console.log(festivalPrice(100000)); // 90000دو تابع بازگشتی، محیطهای مربوط به فراخوانیهای متفاوت را میبینند؛ نرخ یکی با ساختن دیگری عوض نمیشود. Closure مقدارها را همیشه به شکل یک عکس ثابت کپی نمیکند؛ به bindingهای محیط دسترسی دارد و اگر وضعیت مشترک تغییر کند، نتیجه هم میتواند تغییر کند. در مثال حاضر rate پس از ایجاد عوض نشده است. هنگام نگهداشتن Closureهای زیاد، به دادههای بزرگ و ارجاعهایی که بیدلیل زنده میمانند نیز توجه کنید.
تمرین: تابع هزینهٔ مطالعه
تابعی بنویسید که تعداد جلسه و دقیقهٔ هر جلسه را بگیرد و مجموع دقیقه را برگرداند. دقیقهٔ پیشفرض 20 باشد. سپس یک تابع بسازید که مقدار دقیقهٔ هر جلسه را از قبل دریافت کند و فقط تعداد جلسه بگیرد. انتظار داریم هر دو روش برای سه جلسهٔ بیستدقیقهای عدد 60 را بدهند. در پایان توضیح دهید چرا چاپ عدد در داخل تابع، بهتنهایی جای return را نمیگیرد.
function studyTime(sessions, minutes = 20) {
return sessions * minutes;
}
function makeStudyPlanner(minutes) {
return sessions => studyTime(sessions, minutes);
}
const dailyPlan = makeStudyPlanner(20);
console.log(studyTime(3)); // 60
console.log(dailyPlan(3)); // 60اشتباههای رایج
- انتظار مقدار بازگشتی از تابعی که فقط console.log دارد.
- حذف return پس از افزودن آکولاد به Arrow Function.
- قرار دادن نتیجهٔ return در خط بعد بدون توجه به درج خودکار سمیکالن.
- تغییر ناخواستهٔ دادهٔ سراسری داخل تابع محاسباتی.
- انتخاب نام کلی مانند process برای تابعی با یک کار دقیق.
منابع انگلیسی
- MDN — Functions
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
- MDN — Closures
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
- MDN — Arrow function expressions
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
توضیح فارسی و مثالهای محاسباتی برای این مجموعه تدوین شدهاند. در درس آرایهها، تابع را به map، filter و reduce میدهیم تا مجموعهٔ داده را پردازش کنیم.
پیشنیاز و محیط اجرای مثالها
درس 6 از ۲۰؛ پیشنیاز: درسهای قبلی مجموعه. کدهای مستقل این درس در Console تازهٔ مرورگر یا فایل JavaScript اجرا میشوند؛ نمونهها را جداگانه آزمایش کنید.
پیشرفت روی همین مرورگر ذخیره میشود.