توابع

تعریف تابع، Function Declaration و Expression، پارامترها، Arrow Function، Callback، توابع مرتبه بالاتر، Recursion، IIFE، Closure و this با call/apply/bind.

تابع چیست؟

تابع (Function) بلوکی از کد است که یک بار تعریف می‌شود و بارها فراخوانی (Call) می‌شود. توابع کد را قابل استفاده مجدد، خوانا و قابل تست می‌کنند:

// تعریف: اسم تابع + پارامترها + بدنه
function greet(name) {
    return "سلام " + name;
}

// فراخوانی با آرگومان
console.log(greet("علی")); // سلام علی

Function Declaration — اعلان تابع

رایج‌ترین روش تعریف — با کلمه کلیدی function شروع می‌شود:

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

console.log(add(2, 3)); // 5

ویژگی مهم: اعلان‌ها Hoist می‌شوند — یعنی می‌توانید قبل از تعریف آن‌ها را فراخوانی کنید:

console.log(sayHi()); // سلام! (کار می‌کند!)

function sayHi() {
    return "سلام!";
}

Function Expression — عبارت تابع

تابع به‌عنوان مقدار در متغیر ذخیره می‌شود:

const add = function (a, b) {
    return a + b;
};

console.log(add(2, 3)); // 5

تفاوت با Declaration: عبارت‌ها Hoist نمی‌شوند — قبل از تعریف قابل استفاده نیستند:

// console.log(sub(5, 2)); // خطا! Cannot access 'sub' before initialization
const sub = function (a, b) {
    return a - b;
};

پارامترها و آرگومان‌ها

این دو مفهوم را با هم اشتباه نگیرید:

// name و age پارامترند
function introduce(name, age) {
    console.log(name + "، " + age + " ساله");
}

// "سارا" و 25 آرگومانند
introduce("سارا", 25);

جاوا اسکریپت سخت‌گیر نیست — تعداد آرگومان می‌تواند کمتر یا بیشتر باشد:

function f(a, b) {
    console.log(a, b);
}
f(1);        // 1 undefined
f(1, 2, 3);  // 1 2 (سومی نادیده گرفته می‌شود)

آبجکت arguments

در توابع معمولی، آبجکت arguments شامل همه آرگومان‌هاست (شبیه آرایه ولی آرایه نیست):

function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}
console.log(sum(1, 2, 3, 4)); // 10

نکته: arguments در Arrow Function وجود ندارد!

پارامترهای پیش‌فرض (Default Parameters)

اگر آرگومان داده نشود (یا undefined باشد)، مقدار پیش‌فرض استفاده می‌شود:

function greet(name = "مهمان", greeting = "سلام") {
    return greeting + " " + name;
}

console.log(greet());               // سلام مهمان
console.log(greet("علی"));          // سلام علی
console.log(greet("علی", "درود")); // درود علی
console.log(greet(undefined, "هی")); // هی مهمان

مقدار پیش‌فرض می‌تواند عبارت یا حتی پارامتر قبلی باشد:

function calc(width, height = width * 2) {
    return width * height;
}
console.log(calc(5)); // 50 (5 × 10)

Rest Parameters — پارامترهای باقی‌مانده

با ... همه آرگومان‌های باقی‌مانده در یک آرایه واقعی جمع می‌شوند (جایگزین مدرن arguments):

function sum(...numbers) {
    return numbers.reduce((a, b) => a + b, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

// باید آخرین پارامتر باشد:
function log(prefix, ...items) {
    console.log(prefix, items);
}
log("لیست:", "a", "b", "c"); // لیست: ['a', 'b', 'c']

مقدار بازگشتی (return)

دستور return نتیجه را برمی‌گرداند و اجرای تابع را پایان می‌دهد:

function isEven(n) {
    if (n % 2 === 0) {
        return true;   // اینجا تابع تمام می‌شود
    }
    return false;
}
function noReturn() {
    console.log("اجرا می‌شود");
}
console.log(noReturn()); // undefined

Arrow Function — توابع پیکانی

از ES6 — سینتکس کوتاه‌تر با تفاوت‌های مهم:

// سینتکس کامل:
const add = (a, b) => {
    return a + b;
};

// return ضمنی (بدون آکولاد):
const add2 = (a, b) => a + b;

// یک پارامتر — پرانتز لازم نیست:
const double = n => n * 2;

// بدون پارامتر:
const hi = () => "سلام";

// برگرداندن آبجکت (آکولاد در پرانتز):
const makeUser = (name) => ({ name: name });

console.log(double(5));    // 10
console.log(makeUser("علی")); // { name: 'علی' }

تفاوت‌های مهم Arrow با تابع معمولی

ویژگیتابع معمولیArrow Function
this خودش را داردبله (وابسته به فراخوانی)خیر (از محیط بیرونی می‌گیرد)
argumentsداردندارد
constructor (new)داردندارد
hoistingبلهخیر
// مشکل کلاسیک this و حل آن با Arrow:
const timer = {
    seconds: 0,
    start() {
        // this در Arrow همان this متد start است:
        setInterval(() => {
            this.seconds++;
            console.log(this.seconds);
        }, 1000);
    }
};

توابع ناشناس (Anonymous Functions)

توابعی که نام ندارند — معمولاً به‌عنوان آرگومان یا مقدار فوری استفاده می‌شوند:

// به‌عنوان callback:
setTimeout(function () {
    console.log("بعد از ۱ ثانیه");
}, 1000);

// در متدهای آرایه:
const nums = [1, 2, 3];
nums.forEach(function (n) {
    console.log(n);
});

// IIFE:
(function () {
    console.log("بلافاصله اجرا شدم");
})();

Callback — تابع فراخوانی

تابعی که به‌عنوان آرگومان به تابع دیگر داده می‌شود و بعداً فراخوانی می‌شود:

function process(name, callback) {
    console.log("پردازش " + name);
    callback(name); // فراخوانی callback
}

process("داده‌ها", function (n) {
    console.log(n + " پردازش شد ✓");
});

// callbackهای آشنا در متدهای آرایه:
[1, 2, 3].map(n => n * 10);      // [10, 20, 30]
[1, 2, 3].filter(n => n > 1);   // [2, 3]

توابع مرتبه بالاتر (Higher-Order Functions)

تابعی که تابع را می‌گیرد یا تابع برمی‌گرداند:

// ۱) تابع را به‌عنوان آرگومان می‌گیرد:
function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}
repeat(3, i => console.log("تکرار", i));

// ۲) تابع برمی‌گرداند:
function multiplier(factor) {
    return n => n * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

متدهای map، filter، reduce، sort و setTimeout همگی Higher-Order هستند.

توابع سطح اول (First-Class Functions)

در جاوا اسکریپت توابع شهروند درجه یک هستند — یعنی با آن‌ها مانند هر مقدار دیگر رفتار می‌شود:

// در متغیر ذخیره می‌شوند:
const f = function () {};

// در آرایه:
const ops = [n => n + 1, n => n * 2];
console.log(ops[1](5)); // 10

// در ویژگی شیء (متد):
const obj = { run: () => console.log("run!") };
obj.run();

// به تابع دیگر داده می‌شوند و از آن دریافت می‌شوند:
[1, 2, 3].map(String); // ['1', '2', '3']

توابع تو در تو (Nested Functions)

تابع داخل تابع — تابع داخلی به متغیرهای تابع بیرونی دسترسی دارد:

function outer() {
    const msg = "از بیرون";

    function inner() {
        console.log(msg); // دسترسی به متغیر بیرونی
    }
    inner();
}
outer(); // از بیرون

// تابع داخلی بیرون از والد مخفی است:
// inner(); // خطا! بیرون در دسترس نیست

بازگشتی (Recursion)

تابعی که خودش را فراخوانی می‌کند — برای مسائل بازگشتی مثل فاکتوریل و پیمایش ساختارهای درختی:

function factorial(n) {
    // شرط پایه (بدون آن، حلقه بی‌نهایت!):
    if (n <= 1) return 1;
    // گام بازگشتی:
    return n * factorial(n - 1);
}
console.log(factorial(5)); // 120 (5×4×3×2×1)

// فیبوناچی:
function fib(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}
console.log(fib(7)); // 13

هر فراخوانی بازگشتی روی Call Stack قرار می‌گیرد — عمق بیش از حد باعث خطای RangeError: Maximum call stack size exceeded می‌شود.

IIFE — تابع با فراخوانی فوری

Immediately Invoked Function Expression — تعریف و اجرا در یک قدم؛ قبل از ماژول‌ها برای ایجاد محدوده خصوصی استفاده می‌شد:

// سینتکس کلاسیک:
(function () {
    const private1 = "خارج از دسترس";
    console.log("اجرا شد");
})();

// با آرگومان:
(function (name) {
    console.log("سلام " + name);
})("علی");

// نسخه Arrow:
(() => console.log("Arrow IIFE"))();

Closure — بستار

یکی از مهم‌ترین مفاهیم جاوا اسکریپت: تابع داخلی به متغیرهای تابع بیرونی حتی بعد از پایان اجرای بیرونی دسترسی دارد:

function counter() {
    let count = 0; // متغیر خصوصی

    return function () {
        count++;
        return count;
    };
}

const inc = counter();
console.log(inc()); // 1
console.log(inc()); // 2
console.log(inc()); // 3
// count فقط از طریق inc قابل دسترسی است!

کاربردها: داده خصوصی (Encapsulation)، کارخانه تابع (Factory)، یادگیری وضعیت در Callback و Debounce.

// کارخانه توابع با Closure:
function makeGreeter(greeting) {
    return name => greeting + " " + name;
}
const salam = makeGreeter("سلام");
const dorood = makeGreeter("درود");
console.log(salam("علی"));   // سلام علی
console.log(dorood("سارا")); // درود سارا

this — اشاره‌گر زمینه اجرا

مقدار this بر اساس نحوه فراخوانی تابع تعیین می‌شود:

نوع فراخوانیمقدار this
متد شیء: obj.method()خودِ obj
تابع معمولی: fn()undefined (strict) / window
constructor: new Fn()آبجکت تازه
Arrow Functionthis محیط تعریف (lexical)
event handlerعنصر رویداد
const user = {
    name: "علی",
    sayName() {
        console.log(this.name); // this = user
    }
};
user.sayName(); // علی

// مشکل گم شدن this:
const ref = user.sayName;
// ref(); // خطا — this دیگر user نیست!

call — فراخوانی با this مشخص

فراخوانی تابع با آرگومان‌های جدا و this دلخواه:

function introduce(job, city) {
    console.log(this.name + "، " + job + "، " + city);
}

const ali = { name: "علی" };
const sara = { name: "سارا" };

introduce.call(ali, "برنامه‌نویس", "تهران");  // علی، برنامه‌نویس، تهران
introduce.call(sara, "طراح", "شیراز");       // سارا، طراح، شیراز

apply — مثل call ولی با آرایه

مانند call است اما آرگومان‌ها را به‌صورت آرایه می‌گیرد:

introduce.apply(ali, ["برنامه‌نویس", "تهران"]);
// مثل call ولی آرگومان‌ها در آرایه

// کاربرد کلاسیک — پیدا کردن بیشینه آرایه:
const nums = [5, 1, 9, 3];
console.log(Math.max.apply(null, nums)); // 9
// روش مدرن:
console.log(Math.max(...nums)); // 9

bind — ساخت نسخه جدید با this ثابت

تابع را فراخوانی نمی‌کند؛ یک نسخه جدید با this متصل برمی‌گرداند:

const aliIntro = introduce.bind(ali);
aliIntro("معلم", "اصفهان"); // علی، معلم، اصفهان

// با پیش‌فرض آرگومان (Partial Application):
const saraDev = introduce.bind(sara, "برنامه‌نویس");
saraDev("مشهد"); // سارا، برنامه‌نویس، مشهد

// حل مشکل گم شدن this:
const bound = user.sayName.bind(user);
bound(); // علی ✓

مقایسه call و apply و bind

متدفراخوانی؟آرگومان‌هاکاربرد اصلی
callفوری ✓جدا: f.call(obj, a, b)قرض‌گیری متد
applyفوری ✓آرایه: f.apply(obj, [a, b])پخش آرایه در آرگومان
bindخیر — نسخه جدید می‌سازدجدا (اختیاری)قفل this در callback

جمع‌بندی

این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.

ابزارهای مرتبط