تعریف کردن یک تابع، به تنهایی باعث اجرا شدنش نمیشه. با تعریف تابع، شما فقط یک اسم برای اون انتخاب میکنید و میگید اگر روزی صدا زده شد، چه کارهایی انجام بده. وقتی تابع رو صدا میزنید (Call / Invoke)، تازه جاوااسکریپت دستبهکار میشه و دستورات داخلش رو با پارامترهایی که بهش دادید اجرا میکنه.
مثلاً تابع square رو که قبلاً ساختیم، اینطوری صدا میزنیم:
square(5);
اینجا ما عدد ۵ رو به عنوان آرگومان به تابع دادیم. تابع اجرا میشه و مقدار ۲۵ رو به ما پس میده.
ورودیهای تابع اصلاً محدود به عدد و متن نیستند؛ شما میتونید یک شیء کامل یا یک آرایه رو هم به عنوان ورودی به یک تابع بفرستید.
به این کد عجیب نگاه کن:
console.log(square(5)); // خروجی: 25
function square(n) {
return n * n;
}
عجیبه نه؟ ما قبل از اینکه تابع رو تعریف کنیم، صدایش زدیم! اما کد بدون هیچ اروری کار میکنه.
دلیلش قابلیتی به اسم هوئیستینگ (Hoisting) یا بالا کشیدن هست. مفسر جاوااسکریپت قبل از اجرای کدها، تمام «اعلانهای تابع» رو به بالاترین بخشِ محدودهای که توش هستند منتقل میکنه. یعنی کد بالا از نظر جاوااسکریپت اینطوری خونده میشه:
// جاوااسکریپت خودکار تابع رو میاره اولِ اولِ کد:
function square(n) {
return n * n;
}
console.log(square(5)); // ۲۵
یک هشدار خیلی مهم: هوئیستینگ فقط و فقط مخصوص اعلان توابع (Function Declarations) هست و روی بیان توابع (Function Expressions) اصلاً کار نمیکنه. کد زیر قطعاً بهت ارور میده:
console.log(square(5)); // ارور! نمیتونی قبل از ساختن متغیر بهش دسترسی داشته باشی ReferenceError
const square = function (n) {
return n * n;
};
یک تابع میتونه داخل بدنه خودش، خودش رو صدا بزنه! به این کار میگن رِکِـرژن یا بازگشت. توی یک تابع بازگشتی، اگر برای تابع اسم گذاشته باشی (چه اعلان باشه چه بیان تابع)، میتونی با همان اسم خودش رو صدا بزنی:
const foo = function bar() {
// داخل اینجا، هم با نام bar و هم با نام foo میتونی همین تابع رو صدا بزنی
};
بازگشت یه جورایی شبیه به حلقهها عمل میکنه؛ هردوشون یک تیکه کد رو چندین بار اجرا میکنند و هردوشون هم حتماً به یک شرطِ خروج نیاز دارند تا توی تله بینهایت نیفتند (که توی توابع بازگشتی بهش میگن خطای پر شدن حافظه یا Stack Overflow).
بیا یک حلقه while ساده رو تبدیل به یک تابع بازگشتی کنیم:
// حالت اول: با حلقه
let x = 0;
while (x < 10) {
x++;
}
// حالت دوم: به صورت بازگشتی
function loop(x) {
if (x >= 10) return; // شرط خروج: اگر ایکس بزرگتر یا مساوی ۱۰ شد، دیگه ادامه نده!
loop(x + 1); // صدا زدن دوباره خودش با یک مقدار بیشتر
}
loop(0); // شروعِ کار
بعضی از الگوریتمها رو نمیشه به همین راحتی با یک حلقه معمولی نوشت. مثلاً فرض کن میخوای تمام شاخهها و برگهای یک ساختار درختی (مثل تگهای DOM مرورگر یا پوشههای تو در توی کامپیوتر) رو بگردی. این کار با تابع بازگشتی مثل آب خوردنه:
function walkTree(node) {
if (node === null) return;
// یک کاری با این شاخه انجام بده...
// حالا خودکار همین تابع رو برای تکتک بچههای این شاخه صدا بزن:
for (const child of node.childNodes) {
walkTree(child);
}
}
توابع بازگشتی برای اجرا شدن از حافظهای به نام استک (Stack) استفاده میکنند؛ یعنی مثل بشقابهای روی هم چیده شده، هر بار که تابع خودش رو صدا میزنه، یک لایه جدید روی لایه قبلی میشینه و تا لایه رویی تموم نشه، لایههای زیرین آزاد نمیشن.
به این مثال باحال نگاه کن تا این رفتار رفتوبرگشتی استک رو درک کنی:
function foo(i) {
if (i < 0) return;
console.log(`شروع دورِ: ${i}`);
foo(i - 1); // صدا زدن لایه بعدی
console.log(`پایان دورِ: ${i}`);
}
foo(3);
اگر این کد رو اجرا کنی، خروجی چشمنوازی رو توی کنسول میبینی:
شروع دورِ: 3
شروع دورِ: 2
شروع دورِ: 1
شروع دورِ: 0
پایان دورِ: 0
پایان دورِ: 1
پایان دورِ: 2
پایان دورِ: 3
دیدی چی شد؟ اول همه "شروع"ها چاپ شدند (لایهها روی هم چیده شدند) و بعد که به تهِ شرط رسید، از داخلیترین لایه شروع کرد به تموم شدن و "پایان"ها رو به ترتیب عکس چاپ کرد!
الگوی IIFE (که بهش میگن ایفی) یک ترفند جذاب برای زمانی هست که میخوای یک تابع رو همان لحظه که میسازی، فوراً اجراش کنی و نیازی هم نباشه براش اسم بگذاری یا توی متغیر ذخیرهاش کنی.
ظاهرش اینطوریه؛ کل تابع رو میگذاری داخل یک پرانتز، و آخرش یک پرانتز باز و بسته دیگه میذاری تا فوراً منفجر (اجرا) بشه!
// یک ایفی ساده و بدون نام
(function () {
console.log("من همین الان ساخته شدم و فوراً هم اجرا شدم!");
})();
// گرفتن خروجی از ایفی و ذخیره در یک متغیر
const value = (function () {
let secret = "دیتا";
return secret;
})();
شاید بگی خب چه کاریه؟ کدهای داخلش رو بدون تابع مینوشتیم! اما ایفی دو تا معجزه میکنه:
ساختن یک محدوده (Scope) اختصاصی: متغیرهایی که داخل ایفی میسازی، کاملاً خصوصی هستند و به هیچ وجه محیط سراسری (Global) کدت رو کثیف نمیکنند و با بقیه متغیرهای اسکریپتت قاطی نمیشن.
تبدیل دستور به عبارت: چون کلِ ساختار تبدیل به یک عبارت (Expression) میشه، میتونی محاسبات پیچیدهای رو انجام بدی و خروجی نهایی رو مستقیم بریزی داخل یک متغیر (مثل متغیر value در مثال بالا).
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://developer.mozilla.org/en-US/docs/Web/JavaScript