Angular Front-end Инженер

Angular Front-end Инженер

Роадмап навыков для прокачки

Самовызывающиеся функции (IIFE)

Язык JavaScript (ES5)ФункцииОбщее

Основная идея

IIFE (Immediately Invoked Function Expression) — это функция, которая объявляется и сразу же вызывается. Используется для создания изолированной области видимости и избежания загрязнения глобального пространства имён.

Ключевые аспекты

  • Немедленный вызов — функция выполняется сразу после объявления
  • Изоляция области видимости — переменные внутри IIFE не попадают в глобальную область
  • Скобки вокруг функции — превращают объявление функции в выражение
  • Паттерн модуля — IIFE лежит в основе модульного паттерна ES5

Синтаксис

js
(function() {
  // код
})();
 
// или
(function() {
  // код
}());

Применение

  • Инкапсуляция кода и данных
  • Создание приватных переменных
  • Избежание конфликтов имён между скриптами
  • Инициализация кода при загрузке

Частые ошибки на собеседованиях

  • Не понимают, зачем нужны внешние скобки
  • Путают IIFE с обычным объявлением функции
  • Не знают, что IIFE был основным способом модуляризации до ES6 modules
  • Забывают про передачу аргументов в IIFE

Введение и проблематика

IIFE (Immediately Invoked Function Expression) — это паттерн в JavaScript, когда функция объявляется и немедленно выполняется. До появления ES6 модулей IIFE был основным способом изоляции кода и создания приватных областей видимости.

IIFE читается как "иффи" (iffy). Термин популяризировал Бен Алман (Ben Alman) в 2010 году.

Проблема глобального пространства имён

В JavaScript до ES6 все переменные, объявленные через var на верхнем уровне, попадали в глобальную область видимости:

Code Example 1: Почему переменные counter и userName доступны через window? В чём проблема такого подхода?

js
// Проблема: всё попадает в window
var counter = 0;
var userName = 'John';
function increment() { counter++; }
 
// Конфликт с другими скриптами!
console.log(window.counter);  // 0
console.log(window.userName); // 'John'

Базовая теория

Синтаксис IIFE

js
// Классический синтаксис
(function() {
  // код выполняется немедленно
  console.log('IIFE выполнена!');
})();
 
// Альтернативный синтаксис (скобки вызова внутри)
(function() {
  console.log('Тоже работает!');
}());

Почему нужны скобки?

Скобки превращают объявление функции (Function Declaration) в выражение (Function Expression):

Code Example 2: Почему второй вариант выдаёт ошибку, а первый работает? Зачем нужны внешние скобки?

js
// ❌ Ошибка синтаксиса
function() {
  console.log('Не сработает');
}();
 
// ✅ Скобки делают это выражением
(function() {
  console.log('Работает!');
})();
⚠️

Без внешних скобок JavaScript интерпретирует function как начало объявления функции, которое не может быть анонимным и не может быть сразу вызвано.

Другие способы создания выражения

js
// Унарные операторы тоже работают (но используются редко)
!function() { console.log('!'); }();
+function() { console.log('+'); }();
-function() { console.log('-'); }();
void function() { console.log('void'); }();

Практические примеры

Изоляция переменных

Code Example 3: Почему во втором примере window.counter возвращает undefined? Что произошло с переменными?

js
// ❌ Переменные в глобальной области
var counter = 0;
var data = [];
 
function add(item) {
  data.push(item);
  counter++;
}
 
// Всё доступно глобально
console.log(window.counter); // 0
console.log(window.data);    // []

Передача аргументов

js
// Передача глобальных объектов в IIFE
(function(window, document, $) {
  // Внутри IIFE параметры — локальные переменные
  // Это быстрее (не нужно искать в цепочке областей видимости)
  // и безопаснее (можно переименовать)
 
  var $button = $('#myButton');
  $button.click(function() {
    console.log('Clicked!');
  });
 
})(window, document, jQuery);

Возврат значения из IIFE

Code Example 4: Что вернёт counter.get() после двух вызовов increment()? Почему counter.count возвращает undefined?

js
// IIFE может возвращать значение
var counter = (function() {
  var count = 0;
 
  return {
    increment: function() { count++; },
    decrement: function() { count--; },
    get: function() { return count; }
  };
})();
 
counter.increment();
counter.increment();
console.log(counter.get()); // 2
console.log(counter.count); // undefined (приватная переменная)

Паттерн модуля (Module Pattern)

IIFE лежит в основе классического паттерна модуля:

Code Example 5: Какие части этого модуля являются приватными, а какие публичными? Почему UserModule.users возвращает undefined?

js
var UserModule = (function() {
  // Приватные переменные
  var users = [];
  var maxUsers = 100;
 
  // Приватные функции
  function validateUser(user) {
    return user.name && user.email;
  }
 
  // Публичный API (возвращаем объект)
  return {
    add: function(user) {
      if (users.length >= maxUsers) {
        throw new Error('Лимит пользователей');
      }
      if (!validateUser(user)) {
        throw new Error('Невалидный пользователь');
      }
      users.push(user);
    },
 
    getAll: function() {
      return users.slice(); // возвращаем копию
    },
 
    count: function() {
      return users.length;
    }
  };
})();
 
// Использование
UserModule.add({ name: 'John', email: 'john@example.com' });
console.log(UserModule.count()); // 1
console.log(UserModule.users);   // undefined (приватно)

IIFE с параметрами

Защита undefined

js
// В старых браузерах undefined можно было перезаписать
undefined = 'hacked';
 
// IIFE защищает от этого
(function(window, document, undefined) {
  // Здесь undefined всегда имеет правильное значение,
  // потому что третий аргумент не передан
 
  if (someVar === undefined) {
    console.log('Переменная не определена');
  }
})(window, document);

Псевдонимы для библиотек

js
// Избежание конфликтов с другими библиотеками
(function($) {
  // Внутри $ точно jQuery, даже если снаружи конфликт
  $(document).ready(function() {
    $('.button').click(handler);
  });
})(jQuery);

IIFE в современном JavaScript

С появлением ES6 модулей и блочной области видимости (let, const) IIFE используется реже, но всё ещё полезен в определённых ситуациях.

Современные альтернативы

js
// ES6 блочная область видимости
{
  let counter = 0;
  const data = [];
  // переменные не попадают наружу
}
 
// ES6 модули
// module.js
let counter = 0;
export function increment() { counter++; }
// переменные изолированы по умолчанию

Когда IIFE всё ещё полезен

js
// 1. Async IIFE для top-level await (до ES2022)
(async function() {
  const data = await fetch('/api/data');
  console.log(await data.json());
})();
 
// 2. Немедленная инициализация со сложной логикой
const config = (function() {
  const env = process.env.NODE_ENV;
  if (env === 'production') {
    return { apiUrl: 'https://api.example.com', debug: false };
  }
  return { apiUrl: 'http://localhost:3000', debug: true };
})();

Пограничные кейсы

IIFE и this

Code Example 6: Что выведет console.log(this) в каждом из этих примеров? Почему результаты разные?

js
// В строгом режиме this = undefined
(function() {
  'use strict';
  console.log(this); // undefined
})();
 
// В нестрогом режиме this = window (в браузере)
(function() {
  console.log(this); // Window
})();
 
// Стрелочная функция как IIFE
(() => {
  console.log(this); // зависит от внешнего контекста
})();

Именованные IIFE

js
// IIFE может иметь имя (полезно для отладки)
(function myIIFE() {
  console.log('Именованная IIFE');
  // Имя доступно только внутри функции
  console.log(typeof myIIFE); // "function"
})();
 
console.log(typeof myIIFE); // "undefined"

Вопросы интервьюера

Q: Что такое IIFE и зачем он нужен?

IIFE — функция, которая выполняется сразу после объявления. Используется для создания изолированной области видимости и избежания загрязнения глобального пространства имён.

Q: Почему нужны скобки вокруг функции?

Скобки превращают объявление функции (Function Declaration) в выражение (Function Expression), которое можно немедленно вызвать.

Q: Как передать аргументы в IIFE?

Аргументы передаются в скобках вызова: (function(arg) { ... })(value);

Q: Актуален ли IIFE в современном JavaScript?

С появлением ES6 модулей и let/const IIFE используется реже, но всё ещё полезен для немедленной инициализации и async IIFE.

Q: Что такое паттерн модуля?

Паттерн, использующий IIFE для создания приватных переменных и возврата публичного API. Основа модульности в ES5.


Источники

Code Example 1: Проблема глобальной области видимости

❓ Почему переменные counter и userName доступны через window? В чём проблема такого подхода?

js
var counter = 0;
var userName = 'John';
function increment() { counter++; }
 
console.log(window.counter);
console.log(window.userName);

Code Example 2: Синтаксис IIFE

❓ Почему второй вариант выдаёт ошибку, а первый работает? Зачем нужны внешние скобки?

Version A:

js
(function() {
  console.log('Работает!');
})();

Version B:

js
function() {
  console.log('Не сработает');
}();

Code Example 3: Изоляция переменных

❓ Почему во втором примере window.counter возвращает undefined? Что произошло с переменными?

Без IIFE:

js
var counter = 0;
var data = [];
 
function add(item) {
  data.push(item);
  counter++;
}
 
console.log(window.counter);
console.log(window.data);

С IIFE:

js
(function() {
  var counter = 0;
  var data = [];
 
  function add(item) {
    data.push(item);
    counter++;
  }
 
  add('test');
  console.log(counter);
})();
 
console.log(window.counter);
console.log(window.data);

Code Example 4: Возврат значения из IIFE

❓ Что вернёт counter.get() после двух вызовов increment()? Почему counter.count возвращает undefined?

js
var counter = (function() {
  var count = 0;
 
  return {
    increment: function() { count++; },
    decrement: function() { count--; },
    get: function() { return count; }
  };
})();
 
counter.increment();
counter.increment();
console.log(counter.get());
console.log(counter.count);

Code Example 5: Паттерн модуля

❓ Какие части этого модуля являются приватными, а какие публичными? Почему UserModule.users возвращает undefined?

js
var UserModule = (function() {
  var users = [];
  var maxUsers = 100;
 
  function validateUser(user) {
    return user.name && user.email;
  }
 
  return {
    add: function(user) {
      if (users.length >= maxUsers) {
        throw new Error('Лимит пользователей');
      }
      if (!validateUser(user)) {
        throw new Error('Невалидный пользователь');
      }
      users.push(user);
    },
 
    getAll: function() {
      return users.slice();
    },
 
    count: function() {
      return users.length;
    }
  };
})();
 
UserModule.add({ name: 'John', email: 'john@example.com' });
console.log(UserModule.count());
console.log(UserModule.users);

Code Example 6: IIFE и this

❓ Что выведет console.log(this) в каждом из этих примеров? Почему результаты разные?

js
(function() {
  'use strict';
  console.log(this);
})();
 
(function() {
  console.log(this);
})();
 
(() => {
  console.log(this);
})();