توابع
تعریف تابع، 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;
};
پارامترها و آرگومانها
این دو مفهوم را با هم اشتباه نگیرید:
- پارامتر (Parameter): نام متغیر در تعریف تابع
- آرگومان (Argument): مقدار واقعی در فراخوانی تابع
// 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;
}
- بدون return یا return خالی → مقدار
undefinedبرگردانده میشود - کد بعد از return اجرا نمیشود
- return بهتنهایی مقدار undefined برمیگرداند
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 Function | this محیط تعریف (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 |
جمعبندی
- اعلانها Hoist میشوند، عبارتهای تابع نه
- پارامتر در تعریف، آرگومان در فراخوانی
- پارامتر پیشفرض برای آرگومانهای missing،
...برای جمعآوری - Arrow Function this و arguments ندارد — برای callback عالی است
- توابع در جاوا اسکریپت سطح اول هستند — مثل هر مقدار
- Closure دسترسی تابع داخلی به متغیرهای والد حتی بعد از پایان آن
call/applyفوری فراخوانی میکنند با this مشخص؛bindنسخه جدید با this قفلشده میسازد
این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.