۲۵ اکتبر ۲۰۲۲

توابع (Functions)

اغلب اوقات ما نیاز داریم یک مجموعه‌ای از دستورها را در خیلی از جاهای کد چندین بار اجرا کنیم.

برای مثال، می‌خواهیم که پیغامی زیبا برای کسی که وارد صفحه‌ای می‌شود یا خارج می‌شود یا جاهایی دیگر نمایش دهیم.

توابع بلوک‌های ساختمانی اصلی یک برنامه‌اند. آنها به کد اجازه‌ی فراخوانی شدن چند باره را بدون تکرار می‌دهند.

ما مثال‌هایی از توابع درون سیستمی مثل alert(message)، prompt(message, default) و confirm(question) را دیده‌ایم. اما میتوانیم توابع خودمان را هم بسازیم.

تعریف توابع (Function Declaration)

برای ساختن یک تابع ما به تعریف کردن تابع نیاز خواهیم داشت.‌(function declaration)

چیزی شبیه کد پایین:

function showMessage() {
  alert( 'Hello everyone!' );
}

کلمه‌ی کلیدی function اول می‌آید، سپس اسم تابع و سپس لیستی از پارامترها داخل پرانتز (با کاما جدا می‌شوند، در مثال بالا داخل پرانتزها خالی‌ست) و در نهایت کد تابع، با نام “بدنه‌ی تابع”، که توسط دو براکت محصور شده است.

function name(parameter1, parameter2, ... parameterN) {
 // body
}

تابع جدید ما می‌تواند با اسمش صدا زده شود: showMessage().

برای نمونه:

function showMessage() {
  alert( 'Hello everyone!' );
}

showMessage();
showMessage();

فراخوانی showMessage() کد درون تابع را اجرا می‌کند. در اینجا ما پیغام را دوبار خواهیم دید.

این مثال یکی از اهداف اصلی توابع را نشان می‌دهد: اجتناب از کد تکراری.

اگر ما نیاز داشته باشیم نحوه‌ای که پیغام نشان داده می‌شود را عوض کنیم، تنها لازم است که کد را در یک قسمت تغییر دهیم: تابعی که آن را خروجی می‌دهد.

متغیرهای محلی (Local variables)

اگر یک متغیر در درون تابع تعریف شود، فقط در درون همان تابع قابل استفاده است.

برای نمونه:

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // local variable

  alert( message );
}

showMessage(); // Hello, I'm JavaScript!

alert( message ); // <-- Error! The variable is local to the function

متغیرهای بیرونی (Outer variables)

یک تابع می‌تواند به متغیر درونی دسترسی داشته باشد، به عنوان مثال:

let userName = 'John';

function showMessage() {
  let message = 'Hello, ' + userName;
  alert(message);
}

showMessage(); // Hello, John

تابع دسترسی کامل به متغیر بیرونی دارد. همینطور میتواند آنرا تغییر هم بدهد.

برای مثال:

let userName = 'John';

function showMessage() {
  userName = "Bob"; // (1) changed the outer variable

  let message = 'Hello, ' + userName;
  alert(message);
}

alert( userName ); // John before the function call

showMessage();

alert( userName ); // Bob, the value was modified by the function

متغیر بیرونی فقط در مواقعی مورد استفاده قرار می‌گیرد که متغیر محلی‌ای وجود نداشته باشد.

اگر یک متغیر هم‌نام در درون تابع تعریف شود، جانشین متغیر بیرونی می‌شود. برای مثال، در کد زیر، تابع از متغیر محلی userName استفاده می‌کند و متغیر بیرونی نادیده گرفته می‌شود:

let userName = 'John';

function showMessage() {
  let userName = "Bob"; // تعریف یک متغیر محلی

  let message = 'Hello, ' + userName; // Bob
  alert(message);
}

// the function will create and use its own userName
showMessage();

alert( userName ); // John, unchanged, the function did not access the outer variable
متغیرهای سراسری (Global Variables)

متغیرهای تعریف شده بیرون از هر تابعی، مثل userName در کد بالا، سراسری نامیده می‌شوند.

متغیرهای سراسری برای هر تابعی قابل استفاده است (مگر اینکه متغیری محلی آن را تغییر دهد).

معمولا، یک تابع تمام متغیرهای مربوط به کارش را تعریف می‌کند. متغیرهای سراسری فقط اطلاعات سطح-پروژه را ذخیره می‌کنند و مهم است که این متغیرها قابل دسترسی از هرجایی باشند. کدهای جدید متغیرهای سراسری کمی دارند یا اصلا ندارند. اکثر متغیرها در درون تابع‌ هایشان تعریف می‌شوند.

پارامترها

ما می‌توانیم اطلاعات دلخواهی را به توابع با کمک پارامترها پاس بدهیم.

در مثال زیر، تابع دو پارامتر دارد: from و text.

function showMessage(from, text) { // parameters: from, text
  alert(from + ': ' + text);
}

showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)

وقتی تابع در خطوط (*) و (**) صدا زده می‌شود، مقادیر داده شده در متغیرهای محلی from و text کپی می‌شوند. سپس، تابع از آنها استفاده می‌کند.

مثالی دیگر: یک متغیر from داریم و به تابع پاس می‌دهیم. توجه کنید: تابع، from را تغییر می‌دهد، اما تغییر در بیرون دیده نمی‌شود، چراکه تابع همیشه یک کپی از مقدار آن را می‌گیرد:

function showMessage(from, text) {

  from = '*' + from + '*'; // make "from" look nicer

  alert( from + ': ' + text );
}

let from = "Ann";

showMessage(from, "Hello"); // *Ann*: Hello

// the value of "from" is the same, the function modified a local copy
alert( from ); // Ann

وقتی یک مقدار به عنوان یک پارامتر تابع پاس داده می‌شود، به آن آرگومان نیز می‌گویند.

به عبارتی دیگر، بگذارید این مقررات را تعیین کنیم:

  • یک پارامتر یک متغیر لیست شده درون پرانتز‌های تعریف تابع است (یک عبارت مخصوص زمان تعریف).
  • یک آرگومان مقداری‌ست که به تابع موقع صدازدن آن پاس داده شده است (یک عبارت مخصوص زمان فراخوانی).

ما توابع را با پارامترهای‌شان تعریف می‌کنیم، و سپس آن‌ها را با آرگومان‌هایشان صدا می‌زنیم.

در مثال بالا، می‌توانید بگویید: «تابع ‍sayMessage با دو پارامتر تعریف شده، پس با دو آرگومان صدا زده می‌شود: from و Hello».

مقادیر پیش‌فرض

اگر پارامتری فراهم نشده باشد، مقادیر آن undefined می‌شوند.

برای مثال، تابع showMessage(from, text)، می‌تواند با یک آرگومان صدا زده شود:

showMessage("Ann");

این یک خطا نیست. خروجی این فراخوانی "Ann: undefined" است. text نداریم پس پیش‌فرض این است که text === undefined.

اگر ما بخواهیم یک مقدار “پیش‌فرض” برای text در این حالت استفاده کنیم، می‌توانیم آن را بعد از = مشخصش کنیم:

function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}

showMessage("Ann"); // Ann: no text given

حالا اگر پارامتر text پاس داده نشود، مقدار "no text given" را می‌گیرد.

همچنین اگر پارامتر وجود داشته باشد نیز ممکن است مقدار پیش‌فرض قرار بگیرد، در صورتی که برابر با undefined باشد، مانند زیر:

showMessage("Ann", undefined); // Ann: no text given

اینجا "no text given" یک رشته است، اما می‌تواند عبارت پیچیده‌تری باشد، که تنها در حالتی ارزیابی و مقداردهی می‌شود که پارامتری وجود نداشته باشد. بنابراین این هم ممکن است:

function showMessage(from, text = anotherFunction()) {
  // anotherFunction() only executed if no text given
  // its result becomes the value of text
}
ارزیابی پارامترهای پیش‌فرض

در جاوااسکریپت، یک پارامتر پیش‌فرض هربار که تابع بدون پارامتر مریوطه صدا زده بشود، ارزیابی می‌شود.

در مثال بالا، anotherFunction() هربار که showMessage() بدون پارامتر text صدا زده بشود، فراخوانی می‌شود.

از سوی دیگر، این به طور مستقل فراخوانی می‌شود وقتی text وجود نداشته باشد.

پارامترهای پیش‌فرض جایگزین

پارامترهای پیش‌فرض در کد جاوااسکریپت قدیمی

چند سال پیش، جاوااسکریپت از سینتکس برای پارامترهای پیش‌فرض پشتیبانی نمی‌کرد. پس افراد از روش‌های دیگر برای تعیین آن‌ها استفاده می‌کردند.

امروزه، در اسکریپت‌های قدیمی می‌توانیم با آن‌ها برخورد داشته باشیم.

برای مثال، یک بررسی ضمنی برای undefined:

function showMessage(from, text) {
  if (text === undefined) {
    text = 'متنی داده نشده';
  }

  alert( from + ": " + text );
}

…یا استفاده از عمگر ||:

function showMessage(from, text) {
  // بود، مقدار پیش‌فرض را تخصیص بده falsy از نوع text اگر مقدار
  // با وجود نداشتن متن یکسان است text == "" این یعنی
  text = text || 'متنی داده نشده';
  ...
}

گاهی اوقات این منطقی است که مقدارهای پیش‌فرض پارامترها را در مرحله‌ای بعد از تعریف تابع قرار دهیم.

برای بررسی یک پارامتر حذف شده، می‌توانیم آن را با undefined مقایسه کنیم:

function showMessage(text) {
  // ...

  if (text === undefined) { // if the parameter is missing
    text = 'empty message';
  }

  alert(text);
}

showMessage(); // empty message

…یا می‌توانستیم از عملگر || استفاده کنیم:

function showMessage(text) {
  // if text is undefined or otherwise falsy, set it to 'empty'
  text = text || 'empty';
  ...
}

موتورهای جاوااسکریپت مدرن از عملگر nullish coalescing ?? پشتیبانی می‌کنند، این عملگر زمانی که مقدارهای falsy مثل 0 معمولی فرض می‌شوند، بهتر است:

function showCount(count) {
  // if count is undefined or null, show "unknown"
  alert(count ?? "unknown");
}

showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown

بازگردانی یک مقدار (Returning a value)

یک تابع می‌تواند مقداری را در فراخوانی کد به عنوان یک جواب بازگرداند.

ساده‌ترین مثال یک تابعی‌ست که جمع دو عدد را حساب می‌کند:

function sum(a, b) {
  return a + b;
}

let result = sum(1, 2);
alert( result ); // 3

return میتواند در هرجایی از تابع باشد. وقتی اجرای تابع به آن می‌رسد، تابع متوقف می‌شود و مقدار به کد صدازده شده، بازگردانده ‌می‌شود (که در کد بالا result است.)

return ممکن است در یک تابع بارها ظاهر شود. برای مثال:

function checkAge(age) {
  if (age >= 18) {
    return true;
  } else {
    return confirm('Do you have permission from your parents?');
  }
}

let age = prompt('How old are you?', 18);

if ( checkAge(age) ) {
  alert( 'Access granted' );
} else {
  alert( 'Access denied' );
}

همچنین ممکن است که return را بدون مقدار استفاده کرد. این باعث می‌شود که تابع در همان لحظه خارج شود.

برای مثال:

function showMovie(age) {
  if ( !checkAge(age) ) {
    return;
  }

  alert( "Showing you the movie" ); // (*)
  // ...
}

در کد بالا، اگر checkAge(age)، false برگرداند، سپس، showMovie به alert نمی‌رسد.

یک تابع با مقدار خالی return یا بدون آن، undefined برمی‌گرداند.

اگر یک تابع مقداری را برنگرداند، مثل این می‌ماند که undefined را برگردانده باشد:

function doNothing() { /* empty */ }

alert( doNothing() === undefined ); // true

مقدار return خالی، مثل return undefined است:

function doNothing() {
  return;
}

alert( doNothing() === undefined ); // true
هرگز خط خالی بین return و مقدار نگذارید

برای جمله‌ای طولانی در return، شاید وسوسه کننده به نظر برسد که در یک خطی جدا بدین شکل بگذاریم:

return
 (some + long + expression + or + whatever * f(a) + f(b))

اما این کار نمی‌کند چون جاوااسکریپت بعد return یک ; فرض می‌گیرد. مثل:

return;
 (some + long + expression + or + whatever * f(a) + f(b))

بنابراین، به یک بازگردانی خالی تبدیل می‌شود.

اگر ما بخواهیم که عبارت برگردانده شده در چندین خط باشد، باید آن را در همان خط return آغاز کنیم. یا حداقل پرانتز اول (باز شونده) را آنجا بگذاریم، مانند کد زیر: