Skip to main content

Що таке 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

Швидкий приклад

javascript
// Без 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, але зовнішній код до нього не дістанеться.

Типові помилки

Забули зовнішні дужки:

javascript
function() { console.log("hi"); }(); // SyntaxError

Парсер бачить оголошення функції без імені. Виправлення: обгорни в дужки (function() { ... })();

Вважати, що IIFE блокує всі витоки:

javascript
(function() { notDefined = 1; // Без var/let/const! })(); console.log(notDefined); // 1 - потрапив у глобальний scope

Без var, let або const присвоєння створює глобальну змінну. Додай "use strict" першим рядком всередині IIFE, щоб це відловити.

Рекурсивна IIFE через arguments.callee:

javascript
// Не працює в 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:

javascript
// Старий спосіб - 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

javascript
(function() { var privateVar = "існую тільки тут"; console.log(privateVar); // "існую тільки тут" })(); console.log(typeof privateVar); // "undefined" - зникла після IIFE

Змінна створюється, використовується і знищується разом з execution context IIFE. У глобальному scope жодного сліду.

Реальний: jQuery плагін з безпечним псевдонімом $

javascript
// Передаємо 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. Передача аргументу зробила цей підхід безпечним у змішаних кодових базах.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?