دو روش تعریف تابع: Function Declaration و Function Expression

اهداف یادگیری

  • آشنایی با Function Declaration و Function Expression
  • درک تفاوت این دو روش از نظر Hoisting
  • فهم مفهوم Named و Anonymous Function Expression
  • توانایی تشخیص اینکه در چه شرایطی هرکدام مناسب‌تر است

مقدمه

تا اینجا توابع را همیشه به یک شکل خاص تعریف کردیم. اما جاوااسکریپت دو روش اصلی برای تعریف تابع دارد که از نظر ظاهری شبیه به هم به‌نظر می‌رسند، اما رفتار متفاوتی دارند: Function Declaration و Function Expression. تفاوت این دو، به‌ویژه در مبحث Hoisting که در فصل بعدی به‌طور کامل بررسی می‌کنیم، اهمیت زیادی دارد.

Function Declaration

روشی که تا اینجا استفاده کرده‌ایم، Function Declaration نام دارد؛ تابعی که با کلیدواژه‌ی function شروع می‌شود و نام مستقلی دارد:

function greet(name) {
  console.log("سلام " + name);
}

greet("علی");

Function Expression

در Function Expression، تابع به‌عنوان یک مقدار به یک متغیر نسبت داده می‌شود:

const greet = function (name) {
  console.log("سلام " + name);
};

greet("علی");

از دید جاوااسکریپت، تابع در اینجا چیزی شبیه یک مقدار معمولی است که داخل متغیر greet ذخیره می‌شود؛ دقیقاً مثل اینکه یک عدد یا رشته را داخل متغیر بریزیم. این موضوع ریشه در این دارد که در جاوااسکریپت، توابع شهروند درجه‌یک (First-Class) هستند؛ مفهومی که در فصل توابع پیشرفته با جزئیات کامل بررسی می‌شود.

تفاوت کلیدی: Hoisting

مهم‌ترین تفاوت عملی این دو روش، رفتار آن‌ها پیش از رسیدن خط اجرا به تعریف تابع است:

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

کد بالا بدون هیچ خطایی اجرا می‌شود؛ چون Function Declaration به‌طور کامل Hoist می‌شود، یعنی جاوااسکریپت پیش از اجرای کد، کل تعریف تابع را به بالای Scope منتقل می‌کند. اما همین رفتار را با Function Expression امتحان کنید:

فراخوانی Function Expression پیش از تعریف آن
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

در نمونه‌ی دوم، با اینکه sayHi در نهایت یک تابع را نگه می‌دارد، اما چون با const تعریف شده، تا رسیدن خط اجرا به تعریف آن، متغیر sayHi در دسترس نیست (به این وضعیت Temporal Dead Zone می‌گویند). به همین دلیل تلاش برای فراخوانی آن پیش از تعریف، با خطا مواجه می‌شود. جزئیات کامل این رفتار و دلیل دقیق آن را در فصل بعدی، هنگام بررسی Hoisting و Temporal Dead Zone، به‌طور عمیق توضیح خواهیم داد؛ در این مرحله فقط کافی است تفاوت رفتاری این دو روش را به خاطر بسپارید.

Named و Anonymous Function Expression

Function Expression می‌تواند نام داشته باشد یا بدون نام (Anonymous) باشد:

// Anonymous Function Expression
const greet = function (name) {
  console.log("سلام " + name);
};

// Named Function Expression
const greetNamed = function sayHello(name) {
  console.log("سلام " + name);
};

در حالت Named، نام تابع (sayHello) فقط داخل بدنه‌ی خودِ تابع قابل دسترسی است، نه در Scope بیرونی. این ویژگی بیشتر برای خوانایی پیام خطا در Stack Trace هنگام دیباگ کردن مفید است و کاربرد رایج روزمره ندارد.

جدول مقایسه

ویژگیFunction DeclarationFunction Expression
شروع با function و نام مستقلبلهخیر (نسبت به متغیر داده می‌شود)
Hoisting کامل (قابل فراخوانی پیش از تعریف)بلهخیر
می‌تواند بدون نام باشدخیربله

اشتباهات رایج

  • فراخوانی یک Function Expression پیش از رسیدن خط اجرا به تعریف آن، با این تصور که مثل Function Declaration رفتار می‌کند.
  • گیج شدن بین این دو روش هنگام خواندن کدهای دیگران، به‌خصوص وقتی تابع به یک const یا let نسبت داده شده است.

Best Practices

  • برای توابع اصلی و مستقل برنامه که باید در همه‌جای فایل قابل دسترسی باشند، Function Declaration مناسب‌تر است.
  • برای توابعی که به‌عنوان مقدار به متغیر، Property یک شیء یا آرگومان تابع دیگر پاس داده می‌شوند، Function Expression طبیعی‌تر است.
  • در کدنویسی مدرن، Function Expression معمولاً به شکل Arrow Function نوشته می‌شود که در درس بعدی بررسی می‌کنیم.

ارتباط با سایر مفاهیم

تفاوت رفتاری این دو روش، مستقیماً به مبحث Hoisting و Temporal Dead Zone در فصل Execution Model مرتبط است. همچنین، درک اینکه تابع می‌تواند مثل یک مقدار معمولی به متغیر نسبت داده شود، پیش‌زمینه‌ی لازم برای فهم First-Class Functions در فصل توابع پیشرفته است.

جمع‌بندی

Function Declaration با کلیدواژه‌ی function و نامی مستقل تعریف می‌شود و به‌طور کامل Hoist می‌شود، یعنی پیش از تعریف نیز قابل فراخوانی است. Function Expression تابعی است که به یک متغیر نسبت داده می‌شود و تا رسیدن اجرای کد به آن خط، قابل استفاده نیست. انتخاب بین این دو روش، به ساختار و نیاز کد بستگی دارد.