اجرای فوری تابع بلافاصله پس از تعریف: IIFE

مقدمه

در طول این فصل، چند بار به الگویی اشاره کردیم که یک تابع بلافاصله پس از تعریف شدن، فراخوانی می‌شود؛ برای نمونه در فصل Closure، هنگام حل مشکل var در حلقه. در این درس، این الگو را به‌طور رسمی و کامل بررسی می‌کنیم: IIFE یا Immediately Invoked Function Expression (عبارت تابعی که بلافاصله فراخوانی می‌شود).

IIFE چیست؟

IIFE به تابعی گفته می‌شود که در همان لحظه‌ی تعریف شدن، بدون نیاز به ذخیره در متغیر یا فراخوانی جداگانه، اجرا می‌شود:

ساده‌ترین شکل یک IIFE
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

این کد از دو بخش تشکیل شده: ابتدا یک Function Expression که داخل پرانتز قرار گرفته: (function () { ... })، و سپس یک جفت پرانتز دیگر بلافاصله بعد از آن: () که همان تابع را فراخوانی می‌کند. نتیجه‌ی نهایی این است که تابع، همان لحظه که تعریف می‌شود، اجرا هم می‌شود؛ بدون این‌که هرگز در جایی ذخیره شود یا اسمی داشته باشد که بعداً بتوان دوباره آن را صدا زد.

چرا پرانتز اول لازم است؟

اگر بخواهیم function () {...}() را بدون پرانتز دور تابع بنویسیم، جاوااسکریپت خطای Syntax می‌دهد؛ چون کلیدواژه‌ی function در ابتدای یک خط، به‌طور پیش‌فرض به‌عنوان شروع یک Function Declaration تفسیر می‌شود، نه Function Expression. اما همان‌طور که در فصل توابع مبانی دیدیم، Function Declaration باید حتماً یک نام داشته باشد؛ به همین دلیل جاوااسکریپت با خطا مواجه می‌شود.

با قرار دادن پرانتز دور تعریف تابع، به جاوااسکریپت می‌گوییم که این یک عبارت (Expression) است، نه یک Declaration مستقل؛ دقیقاً همان تکنیکی که در فصل Arrow Function، هنگام بازگرداندن یک Object با Implicit Return دیدیم (() => ({ name: name })) از همین اصل استفاده می‌کرد.

چند فرم رایج IIFE

IIFE را می‌توان به چند شکل مختلف نوشت که همگی از نظر عملکرد یکسان هستند:

// فرم اول: پرانتز دور کل عبارت
(function () {
  console.log("فرم اول");
})();

// فرم دوم: پرانتز فراخوانی داخل پرانتز بیرونی
(function () {
  console.log("فرم دوم");
}());

// فرم سوم: با Arrow Function
(() => {
  console.log("فرم سوم");
})();

هر سه فرم بالا دقیقاً همان رفتار را دارند؛ انتخاب بین آن‌ها بیشتر یک سلیقه‌ی نگارشی است.

کاربرد اصلی: ساخت یک Scope مستقل و ایزوله

مهم‌ترین دلیل استفاده از IIFE، ساخت یک Scope کاملاً مستقل است که متغیرهای داخل آن، به Global Scope یا هر Scope بیرونی دیگری نشت نمی‌کنند:

ایزوله کردن متغیرها با IIFE
خروجی کنسول اینجا نمایش داده می‌شود...

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

همان‌طور که مشاهده می‌کنید، secretValue کاملاً داخل Scope تابع IIFE محبوس می‌ماند و هیچ ردی از آن در Global Scope باقی نمی‌ماند؛ به همین دلیل typeof secretValue در بیرون، مقدار "undefined" برمی‌گرداند. این الگو، پیش از رواج ES Modules (که در فصل Modules بررسی خواهیم کرد)، رایج‌ترین روش جلوگیری از آلوده شدن Global Scope در کتابخانه‌ها و اسکریپت‌های قدیمی‌تر جاوااسکریپت بود.

کاربرد دوم: اجرای یک‌بار و فوری برای مقداردهی اولیه

گاهی نیاز داریم یک محاسبه یا مقداردهی اولیه‌ی پیچیده را فقط یک‌بار انجام دهیم و نتیجه‌ی نهایی را در یک متغیر ذخیره کنیم؛ IIFE می‌تواند مستقیماً یک مقدار برگرداند:

const config = (function () {
  let baseValue = 10;
  let multiplier = 5;
  return baseValue * multiplier;
})();

console.log(config); // 50

در این مثال، متغیرهای baseValue و multiplier فقط برای محاسبه‌ی یک‌باره‌ی config لازم بودند و پس از پایان اجرای IIFE، دیگر در جایی از کد قابل دسترسی نیستند؛ فقط نتیجه‌ی نهایی در config باقی می‌ماند.

کاربرد سوم: راه‌حل مشکل Closure در حلقه (مرور از فصل قبل)

همان‌طور که در فصل Closure دیدیم، پیش از رواج let، IIFE برای ساخت یک Scope مستقل به‌ازای هر تکرار حلقه استفاده می‌شد:

for (var i = 1; i <= 3; i++) {
  (function (capturedI) {
    setTimeout(function () {
      console.log(capturedI);
    }, 100);
  })(i);
}

در این الگو، هر بار که حلقه اجرا می‌شود، یک IIFE جدید با آرگومان i فراخوانی می‌شود؛ چون هر فراخوانی تابع (طبق چیزی که در فصل Execution Model درباره‌ی Function Execution Context یاد گرفتیم)، پارامتر capturedI مستقل خودش را می‌سازد، مشکل اشتراک متغیر که در آن درس بررسی کردیم، به‌طور کامل برطرف می‌شود.

آیا IIFE امروزه هنوز کاربرد دارد؟

با معرفی let/const (که مشکل Scope حلقه را حل کردند) و ES Modules (که مشکل آلودگی Global Scope را به‌طور رسمی حل می‌کنند)، بسیاری از کاربردهای تاریخی IIFE دیگر ضرورت گذشته را ندارند. با این حال، IIFE هنوز هم در سناریوهایی مثل مقداردهی اولیه‌ی یک‌باره یا کدهای مستقل کوچک (مثلاً یک اسکریپت مستقل که باید بلافاصله اجرا شود و هیچ متغیری در Global Scope باقی نگذارد) کاربرد دارد.

جدول جمع‌بندی

ویژگیتوضیح
تعریفتابعی که بلافاصله پس از تعریف شدن، فراخوانی می‌شود
دلیل نیاز به پرانتز دور تابعتبدیل Function Declaration به Function Expression
کاربرد اصلی تاریخیایزوله کردن متغیرها از Global Scope
وضعیت امروزیتا حد زیادی جای خود را به let/const و ES Modules داده، اما هنوز کاربردهای خاص دارد

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

IIFE به‌طور مستقیم بر پایه‌ی مفاهیم Function Expression (از فصل توابع مبانی) و Lexical Scope (از فصل Execution Model) بنا شده است. این الگو، آخرین حلقه‌ی این فصل توابع پیشرفته است؛ در فصل بعدی، به سراغ this و Binding می‌رویم که رفتار توابع را از زاویه‌ای کاملاً متفاوت -یعنی نحوه‌ی تعیین مقدار this هنگام فراخوانی- بررسی خواهد کرد.

جمع‌بندی

IIFE (Immediately Invoked Function Expression) تابعی است که بلافاصله پس از تعریف، بدون نیاز به فراخوانی جداگانه اجرا می‌شود؛ برای این کار، تعریف تابع باید داخل پرانتز قرار گیرد تا به‌عنوان Expression تفسیر شود، نه Declaration. کاربرد اصلی تاریخی IIFE، ایزوله کردن متغیرها از Global Scope بوده است؛ کاربردی که امروزه بیشتر توسط let/const و ES Modules پوشش داده می‌شود، هرچند IIFE همچنان برای مقداردهی اولیه‌ی یک‌باره یا کدهای مستقل کاربرد دارد.