IIFE

Зразу закличний вираз функції (IIFE) – це функція JavaScript, що виконується одразу після свого визначення. Назву IIFE пропонує Бен Альман у своєму блозі.

(function () {
  // …
})();

(() => {
  // …
})();

(async () => {
  // …
})();

Це патерн проєктування, який також відомий як самовиклична анонімна функція і має дві основні частини:

  1. Перша з них – це анонімна функція з лексичною областю видимості, обмежена оператором групування (). Це запобігає звертанням до змінних всередині IIFE та забрудненню глобальної області видимості.
  2. Друга частина створює негайно закличний вираз функції (), завдяки чому рушій JavaScript зразу ж інтерпретує функцію.

Ситуації для використання

Уникання забруднення глобального простору імен

Оскільки наш застосунок може містити чимало функцій і глобальних змінних з різних файлів коду, важливо обмежувати кількість глобальних змінних. Якщо є якийсь код ініціалізації, який немає потреби використовувати знову, то можна скористатися патерном IIFE. Оскільки цей код не використовуватиметься знову, то використання IIFE в цьому випадку краще, ніж використання оголошення функції або виразу функції.

(() => {
  // якийсь ініціалізаційний код
  let firstVariable;
  let secondVariable;
})();

// firstVariable і secondVariable будуть викинуті після виконання функції.

Виконання асинхронної функції

IIFE з async дає змогу користуватися await і for-await навіть у старих браузерах і середовищах JavaScript, які не мають await на верхньому рівні:

const getFileStream = async (url) => {
  // реалізація
};

(async () => {
  const stream = await getFileStream("https://domain.name/path/file.ext");
  for await (const chunk of stream) {
    console.log({ chunk });
  }
})();

Патерн модуля

Також IIFE можна використовувати для створення приватних і публічних змінних і методів. Складніше використання патерну модуля та інші застосування IIFE можна прочитати в книзі Едді Османі "Learning JavaScript Design Pattern".

const makeWithdraw = (balance) =>
  ((copyBalance) => {
    let balance = copyBalance; // Ця змінна є приватною
    const doBadThings = () => {
      console.log("Я зроблю недобрі речі з вашими грошима");
    };
    doBadThings();
    return {
      withdraw(amount) {
        if (balance >= amount) {
          balance -= amount;
          return balance;
        }
        return "Недостатньо грошей";
      },
    };
  })(balance);

const firstAccount = makeWithdraw(100); // "Я зроблю недобрі речі з вашими грошима"
console.log(firstAccount.balance); // undefined
console.log(firstAccount.withdraw(20)); // 80
console.log(firstAccount.withdraw(30)); // 50
console.log(firstAccount.doBadThings); // undefined; цей метод є приватним
const secondAccount = makeWithdraw(20); // "Я зроблю недобрі речі з вашими грошима"
console.log(secondAccount.withdraw(30)); // "Недостатньо грошей"
console.log(secondAccount.withdraw(20)); // 0

Для циклу for з var до ES6

У старому коді, з часів до запровадження інструкцій let і const в ES6 і блокової області видимості, можна зустріти наступне застосування IIFE. Для інструкції var існують лише функційні області видимості та глобальна. Припустімо, що потрібно створити 2 кнопки з текстом, відповідно, Кнопка 0 і Кнопка 1, і при натисканні на них вони мають виводити повідомлення 0 і 1. Наступний код не працює:

for (var i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Кнопка ${i}`;
  button.onclick = function () {
    console.log(i);
  };
  document.body.appendChild(button);
}
console.log(i); // 2

Коли їх клацнути, то кнопки 0 і 1 виведуть 2, оскільки i є глобальною змінною з останнім значенням 2. Щоб виправити цю проблему до ES6, можна було скористатися патерном IIFE:

for (var i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Кнопка ${i}`;
  button.onclick = (function (copyOfI) {
    return function () {
      console.log(copyOfI);
    };
  })(i);
  document.body.appendChild(button);
}
console.log(i); // 2

Коли їх клацнути, то кнопки 0 і 1 виведуть 0 і 1. Змінна i оголошена глобально. За допомогою інструкції let можна зробити просто так:

for (let i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Кнопка ${i}`;
  button.onclick = function () {
    console.log(i);
  };
  document.body.appendChild(button);
}
console.log(i); // Uncaught ReferenceError: i is not defined.

Коли їх клацнути, ці кнопки виведуть 0 і 1.

Дивіться також