Що таке IIFE (immediately invoked Function expression) в JavaScript
IIFE (Immediately Invoked Function Expression) - це функція, яка визначається і запускається одразу, створюючи ізольований scope (область видимості), що зникає після виконання.
Теорія
TL;DR
- IIFE - це як звуконепроникна кабінка: код виконується всередині, змінні залишаються всередині, нічого не витікає в глобальний scope
- Головна відмінність від звичайних функцій: запускається автоматично, коли парсер до неї доходить, а не коли її викликаєш
- Використовуй для одноразової ініціалізації, приватних змінних або безпечної передачі глобалів у замкнений scope
- Після ES6:
import/exportіletу блоках покривають більшість випадків, але IIFE досі зустрічається в legacy-коді і в output Babel
Швидкий приклад
// Без IIFE - var витікає в глобальний scope
var count = 0;
console.log(count); // 0 - видимий звідусіль
// З IIFE - scope залишається замкненим
(function() {
var count = 0;
count++;
console.log(count); // 1
})();
console.log(typeof count); // "undefined" - не витікЗовнішні дужки перетворюють оголошення функції на вираз. Дужки () в кінці одразу її викликають. Ось і весь синтаксис.
Як працює ізоляція scope
Коли JavaScript-рушій зустрічає IIFE, він створює новий execution context (контекст виконання) з власним середовищем змінних. Функція виконується, змінні живуть у цьому контексті, а після завершення контекст знищується разом зі змінними. Назовні нічого не виходить, якщо явно не повертаєш або не присвоюєш щось зовні.
Саме тому var всередині IIFE не потрапляє в window: змінна існує в scope функції, а не в глобальному. Той самий механізм, що лежить в основі замикань (closures), діє і тут.
Коли використовувати
- Одноразова ініціалізація (налаштування конфігурації, підвантаження поліфілів) - виконати один раз, без залишкових змінних
- Безпечна передача глобалів:
(function($) { ... })(jQuery)фіксує псевдонім$незалежно від зовнішніх конфліктів - Legacy-патерни модулів (AMD/UMD обгортки) до появи ES6 modules
- Обгортання сторонніх скриптів, де не контролюєш глобальний namespace
Пропускай IIFE, коли є ES6 modules (import/export), або коли потрібен просто блочний scope ({ let x = 1; }).
Як це обробляє рушій
Зовнішні дужки кажуть парсеру: це вираз, не оголошення. Без них function() {}() кидає SyntaxError, бо парсер очікує ім'я після function. Далі V8 додає IIFE до call stack, виконує в новому execution context і знімає зі стеку. Scope chain, який вона створила, зникає. Замикання (closures) всередині (наприклад, callback у setTimeout) зберігають посилання на цей scope, але зовнішній код до нього не дістанеться.
Типові помилки
Забули зовнішні дужки:
function() { console.log("hi"); }(); // SyntaxErrorПарсер бачить оголошення функції без імені. Виправлення: обгорни в дужки (function() { ... })();
Вважати, що IIFE блокує всі витоки:
(function() {
notDefined = 1; // Без var/let/const!
})();
console.log(notDefined); // 1 - потрапив у глобальний scopeБез var, let або const присвоєння створює глобальну змінну. Додай "use strict" першим рядком всередині IIFE, щоб це відловити.
Рекурсивна IIFE через arguments.callee:
// Не працює в strict mode
var fib = (function(n) {
if (n <= 1) return n;
return arguments.callee(n-1) + arguments.callee(n-2); // Deprecated
})(10);
// Виправлення: іменований function expression
var fib = (function f(n) {
return n <= 1 ? n : f(n-1) + f(n-2);
})(10);arguments.callee заблокований у strict mode. Іменований function expression f посилається на себе без цієї проблеми.
Використовувати IIFE для замикань у циклах, коли є let:
// Старий спосіб - IIFE для фіксації змінної циклу
for (var i = 0; i < 3; i++) {
(function(j) { setTimeout(() => console.log(j), 0); })(i);
}
// Сучасний спосіб - просто let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}Де зустрічається в реальному коді
- jQuery плагіни:
(function($) { $.fn.myWidget = function() { ... }; })(jQuery)був стандартним патерном роками - Вихідний код Lodash/Underscore: IIFE передає
rootіfreeGlobalдля визначення AMD/CommonJS - Babel output: досі огортає транспільовані модулі в IIFE для ізоляції в браузері
- Поліфіл-скрипти:
(function() { if (!window.Promise) { /* завантажити поліфіл */ } })();
Цей патерн зустрічається скрізь у pre-ES6 кодових базах. Як тільки ES6 modules запрацювали в браузерах, нові проекти перестали їх писати, але в legacy-коді вони нікуди не ділись.
Питання на співбесіді
Q: Напиши IIFE, яка повертає функцію-лічильник.
A: var counter = (function() { var count = 0; return function() { return ++count; }; })(); IIFE виконується один раз і повертає внутрішню функцію. count залишається живим, бо функція, що повертається, закриває його у замикання: counter() поверне 1, потім 2, і так далі.
Q: Чому IIFE були важливими до ES6?
A: var має function scope, а не блочний. У великих застосунках з багатьма скриптами кожен var на верхньому рівні ставав властивістю window. IIFE був єдиним надійним способом створити приватний scope без системи модулів.
Q: Яка різниця між стрілковою IIFE і класичною?
A: (() => { ... })() підходить для більшості випадків. Стрілкові функції не мають свого this і arguments, тому класична форма потрібна, коли логіка всередині від них залежить.
Q: Callback у setTimeout всередині IIFE виконується після завершення IIFE. Він тримає scope живим?
A: Так. Callback закриває scope IIFE у замикання. Execution context вже знищений, але замикання тримає прив'язки змінних живими до моменту виконання callback і подальшого збирання сміття.
Q: (Senior) Як написати UMD-обгортку через IIFE для середовища без бандлера?
A: (function(root, factory) { if (typeof define === 'function') define(factory); else root.MyLib = factory(); })(this, function() { return { /* публічне API */ }; }); Це визначає AMD в рантаймі і фолбечиться на глобальний scope, якщо AMD немає.
Приклади
Базовий: ізоляція scope
(function() {
var privateVar = "існую тільки тут";
console.log(privateVar); // "існую тільки тут"
})();
console.log(typeof privateVar); // "undefined" - зникла після IIFEЗмінна створюється, використовується і знищується разом з execution context IIFE. У глобальному scope жодного сліду.
Реальний: jQuery плагін з безпечним псевдонімом $
// Передаємо jQuery як аргумент - фіксуємо псевдонім $
// навіть якщо інша бібліотека теж на нього претендує
(function($) {
$.fn.tooltip = function(options) {
return this.each(function() {
var el = $(this);
el.attr("title", options.text || "default");
});
};
})(jQuery);
// Використання
$(".btn").tooltip({ text: "Натисни мене" });Це патерн, який використовували DataTables, Select2 і більшість jQuery-плагінів. $ всередині IIFE завжди посилається на jQuery, незалежно від того, що означає $ у зовнішньому scope. Передача аргументу зробила цей підхід безпечним у змішаних кодових базах.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.