Пошук уроків, статей та іншого контенту
Познайомтеся з функціями, які виконуються одразу після створення. Дізнайтеся, навіщо IIFE активно використовували до появи ES Modules і в яких ситуаціях цей патерн усе ще можна зустріти.
IIFE (Immediately Invoked Function Expression) — це функціональний вираз, який виконується одразу після створення.
Звичайну функцію потрібно спочатку оголосити, а потім викликати:
function greet() {
console.log("Привіт!");
}
greet();IIFE поєднує створення функції та її виклик в одному виразі:
(function () {
console.log("Привіт!");
})();У цьому прикладі:
function () { ... } створює функцію;
зовнішні дужки перетворюють оголошення функції на функціональний вираз;
останні () одразу викликають цей вираз.
Найпоширеніший варіант IIFE:
(function () {
// Код виконається одразу
})();Також можна зустріти варіант із дужками після функції:
(function () {
console.log("IIFE виконано");
}());Обидва варіанти працюють однаково. Перший зазвичай легше читати, оскільки дужки виклику явно розташовані після всього функціонального виразу.
IIFE можна створити і зі стрілковою функцією:
(() => {
console.log("Стрілкова IIFE");
})();Якщо функція приймає параметри, їх передають у дужках виклику:
((name) => {
console.log(`Привіт, ${name}!`);
})("Олена");Синтаксис функції без імені не може стояти самостійно як оголошення:
function () {
console.log("Помилка синтаксису");
}JavaScript очікує ім’я після ключового слова function. Тому функцію потрібно перетворити на вираз. Один зі способів — обгорнути її в дужки:
(function () {
console.log("Це функціональний вираз");
})();Дужки повідомляють JavaScript: перед нами не оголошення функції, а значення — функціональний вираз.
Для перетворення на вираз можна використовувати й інші оператори, але вони менш поширені та гірше читаються:
!function () {
console.log("IIFE");
}();У сучасному коді варто надавати перевагу звичайним дужкам.
До появи сучасних модулів IIFE часто використовували для створення локальної області видимості.
Змінні, оголошені всередині IIFE, недоступні за її межами:
(function () {
const secret = "приховане значення";
console.log(secret);
})();
console.log(secret); // ReferenceErrorЦе допомагає:
не створювати зайві глобальні змінні;
уникати конфліктів імен;
приховувати внутрішню реалізацію;
ізолювати код окремого компонента або бібліотеки.
var і IIFEIIFE особливо часто використовували разом із var, оскільки var має функціональну, а не блочну область видимості:
var message = "глобальне повідомлення";
(function () {
var message = "локальне повідомлення";
console.log(message);
})();
console.log(message);Результат:
локальне повідомлення
глобальне повідомленняЗмінна message усередині функції не змінює зовнішню змінну.
Водночас let і const уже мають блочну область видимості. Для простого обмеження області видимості зазвичай достатньо блока:
{
const internalValue = 42;
console.log(internalValue);
}
console.log(internalValue); // ReferenceErrorТому сьогодні IIFE не потрібна лише для того, щоб приховати локальні let або const.
IIFE може приймати аргументи так само, як звичайна функція:
((userName, role) => {
console.log(`${userName} має роль ${role}`);
})("Марія", "редактор");Параметри IIFE часто використовували, щоб передати глобальні об’єкти в локальну область:
(function (globalObject) {
globalObject.appName = "Demo App";
})(globalThis);
console.log(globalThis.appName);globalThis — стандартний спосіб отримати глобальний об’єкт у різних середовищах JavaScript.
У браузері ним є глобальний об’єкт, пов’язаний із window, а в Node.js — глобальний контекст середовища виконання.
IIFE може повертати значення. Оскільки функція виконується одразу, результат можна зберегти у змінну:
const configuration = (() => {
const environment = "production";
return {
environment,
debug: false
};
})();
console.log(configuration.environment);
console.log(configuration.debug);Внутрішня змінна environment була локальною, але повернений об’єкт став доступним через configuration.
До появи ES Modules IIFE часто використовували для створення модулів із приватним станом:
const counter = (() => {
let value = 0;
return {
increment() {
value += 1;
return value;
},
decrement() {
value -= 1;
return value;
},
getValue() {
return value;
}
};
})();
console.log(counter.getValue()); // 0
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1Змінна value недоступна безпосередньо:
console.log(counter.value); // undefinedАле методи об’єкта можуть працювати з нею. Це приклад замикання: внутрішня змінна продовжує існувати, тому що на неї посилаються повернені функції.
Розглянемо практичніший приклад — генератор ідентифікаторів:
const createId = (() => {
let nextId = 1;
return () => {
const currentId = nextId;
nextId += 1;
return currentId;
};
})();
console.log(createId()); // 1
console.log(createId()); // 2
console.log(createId()); // 3Змінити nextId ззовні неможливо. Єдиний доступ до нього відбувається через функцію, що повертає наступний ідентифікатор.
Подібний підхід використовували для:
лічильників;
кешів;
налаштувань;
внутрішнього стану бібліотеки;
приховування допоміжних функцій.
У старому JavaScript-колі код часто завантажували через тег script:
<script src="first.js"></script>
<script src="second.js"></script>Якщо обидва файли оголошували глобальні змінні з однаковими іменами, вони могли конфліктувати.
IIFE ізолювала змінні кожного файлу:
// first.js
(function () {
const helper = "перший файл";
console.log(helper);
})();// second.js
(function () {
const helper = "другий файл";
console.log(helper);
})();Обидві змінні називаються helper, але перебувають у різних функціональних областях видимості.
Якщо частину API потрібно було зробити доступною глобально, її явно додавали до глобального об’єкта:
(function (globalObject) {
function formatPrice(value) {
return `${value.toFixed(2)} грн`;
}
globalObject.formatPrice = formatPrice;
})(globalThis);
console.log(formatPrice(19.5)); // 19.50 грнУтім, надмірне додавання властивостей до глобального об’єкта також могло призводити до конфліктів. Тому часто створювали один глобальний простір імен:
(function (globalObject) {
globalObject.MyApp = globalObject.MyApp || {};
globalObject.MyApp.version = "1.0.0";
})(globalThis);
console.log(globalThis.MyApp.version);IIFE може бути асинхронною. Для цього перед function або стрілковою функцією додають async, а сам виклик також повертає проміс:
(async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const todo = await response.json();
console.log(todo.title);
})();Такий підхід іноді називають async IIFE. Він був особливо корисним до появи підтримки top-level await у JavaScript-модулях.
Помилки в async IIFE можна обробляти через try...catch:
(async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
if (!response.ok) {
throw new Error(`HTTP-помилка: ${response.status}`);
}
const todo = await response.json();
console.log(todo.title);
} catch (error) {
console.error("Не вдалося завантажити дані:", error.message);
}
})();Сьогодні для ізоляції коду зазвичай використовують ES Modules:
// math.js
export function add(a, b) {
return a + b;
}// app.js
import { add } from "./math.js";
console.log(add(2, 3));Модулі мають власну область видимості, тому їхні змінні не стають глобальними автоматично. У сучасному застосунку модуль зазвичай є кращим рішенням, ніж IIFE.
Порівняння:
IIFE — старий, але простий спосіб ізолювати код;
ES Modules — сучасний механізм імпорту, експорту та організації залежностей;
блоки з let і const — простий спосіб створити локальну область видимості всередині одного фрагмента коду.
IIFE все ще можна зустріти:
у старому коді;
у бібліотеках, що підтримують застарілі середовища;
у скриптах, які підключаються без модулів;
у самодостатніх конфігураційних або ініціалізаційних скриптах;
в async IIFE для запуску асинхронного коду з певного контексту;
у згенерованому коді після роботи бандлера.
Для локальних змінних сучасний код часто можна записати без IIFE:
{
const firstName = "Олена";
const lastName = "Коваль";
console.log(`${firstName} ${lastName}`);
}Однак блок не є функцією. Він не має:
власного this;
параметрів;
return;
окремого контексту виклику;
можливості повернути значення безпосередньо.
Якщо потрібно виконати функціональну логіку та отримати результат, IIFE може бути доречною:
const initialState = (() => {
const savedState = "dark";
if (savedState === "dark") {
return { theme: "dark" };
}
return { theme: "light" };
})();
console.log(initialState);Проте в такому випадку іноді краще винести логіку в іменовану функцію:
function getInitialState() {
const savedState = "dark";
return savedState === "dark"
? { theme: "dark" }
: { theme: "light" };
}
const initialState = getInitialState();
console.log(initialState);Іменована функція може бути зрозумілішою та зручнішою для повторного використання й тестування.
IIFE доречна, коли логіку потрібно виконати лише один раз:
const settings = (() => {
console.log("Ініціалізація налаштувань");
return {
language: "uk",
theme: "light"
};
})();Якщо ту саму операцію потрібно виконувати багато разів, краще оголосити звичайну функцію:
function createSettings(language, theme) {
return {
language,
theme
};
}
console.log(createSettings("uk", "light"));
console.log(createSettings("en", "dark"));Функція в IIFE має бути не лише огорнута в дужки, а й викликана:
(function () {
console.log("Цей код не виконається");
});Це просто функціональний вираз, який ніколи не викликається.
Правильно:
(function () {
console.log("Цей код виконається");
})();Дужки виклику мають належати функціональному виразу:
(function () {
console.log("Правильна IIFE");
})();Будь-який код після завершення функції повинен бути написаний уважно, інакше можна випадково викликати не той вираз або отримати помилку синтаксису.
(function () {
const token = "abc";
})();
console.log(token); // ReferenceErrorЯкщо значення потрібно використати зовні, його треба повернути:
const token = (() => {
return "abc";
})();
console.log(token);Не варто обгортати кожен невеликий фрагмент коду в IIFE без потреби:
(() => {
const result = 2 + 2;
console.log(result);
})();Для такого коду IIFE не дає суттєвої користі. Краще використовувати звичайний блок або просто написати код напряму.
this і стрілковою функцієюЗвичайна функція та стрілкова функція по-різному працюють із this:
(function () {
console.log(this);
})();
(() => {
console.log(this);
})();Стрілкова функція не створює власного this, а використовує його із зовнішнього контексту. Якщо логіка IIFE залежить від this, потрібно свідомо обирати тип функції та враховувати режим виконання.
Async IIFE повертає проміс. Якщо помилка виникла всередині й її не перехопити, можна отримати необроблену відмову промісу:
(async () => {
throw new Error("Помилка");
})();Для реального коду використовуйте try...catch або обробляйте повернений проміс:
(async () => {
throw new Error("Помилка");
})().catch((error) => {
console.error(error.message);
});Нижче IIFE створює простий модуль журналювання. Префікс і кількість повідомлень залишаються приватними:
const logger = (() => {
const prefix = "[Application]";
let messageCount = 0;
function write(level, message) {
messageCount += 1;
console.log(`${prefix} ${level}: ${message}`);
}
return {
info(message) {
write("INFO", message);
},
error(message) {
write("ERROR", message);
},
getMessageCount() {
return messageCount;
}
};
})();
logger.info("Застосунок запущено");
logger.error("Не вдалося завантажити профіль");
console.log(`Кількість повідомлень: ${logger.getMessageCount()}`);Зовнішній код має доступ лише до методів, які повернула IIFE. Функція write, змінна prefix і змінна messageCount приховані.
IIFE може бути виправданою, якщо:
код має виконатися один раз одразу після завантаження;
потрібно швидко ізолювати старий скрипт без модулів;
необхідно приховати приватний стан у самодостатньому фрагменті;
потрібно запустити async-код у контексті, де top-level await недоступний;
ви підтримуєте або змінюєте код, який уже побудований на цьому патерні.
Для нового масштабного застосунку зазвичай краще обрати:
ES Modules;
звичайні функції;
блоки з let і const;
класи або інші механізми інкапсуляції, якщо вони справді потрібні.
IIFE — це функція, яка викликається одразу після створення.
Найпоширеніший синтаксис: (function () { ... })();.
Зовнішні дужки перетворюють оголошення функції на функціональний вираз.
IIFE створює власну функціональну область видимості.
Через замикання IIFE може приховувати приватні змінні та повертати публічний API.
До появи ES Modules IIFE активно використовували для створення модулів і захисту від глобальних конфліктів.
Async IIFE дає змогу використовувати await усередині функції, яка запускається одразу.
У сучасному коді ES Modules зазвичай є кращим способом ізоляції та організації залежностей.
IIFE залишається корисною для старого коду, самодостатніх скриптів і одноразової ініціалізації.