Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке IIFE (immediately invoked Function expression) в JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**IIFE** - це функція, яка виконується одразу, коли парсер до неї доходить, створюючи приватний scope, що зникає після виконання. ```javascript (function() { var secret = "прихована"; console.log(secret); // "прихована" })(); console.log(typeof secret); // "undefined" - не потрапила в глобальний scope ``` **Ключове:** зовнішні дужки роблять її виразом; дужки `()` в кінці одразу викликають. Змінні всередині не потрапляють у глобальний scope.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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)](/questions/what-is-closure-in-javascript), діє і тут. ### Коли використовувати - Одноразова ініціалізація (налаштування конфігурації, підвантаження поліфілів) - виконати один раз, без залишкових змінних - Безпечна передача глобалів: `(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)](/questions/what-is-closure-in-javascript) всередині (наприклад, 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. Передача аргументу зробила цей підхід безпечним у змішаних кодових базах.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.