Learning Book

Блочная область видимости и IIFE

var и функциональный скоуп

До ES6 JavaScript имел только функциональный скоуп: var «видна» во всей функции, в которой объявлена. Блоки {} скоупа для var не создают.

function example() {
  if (true) {
    var x = 10  // x видна во всей функции!
  }
  console.log(x) // 10 — не ошибка

  for (var i = 0; i < 3; i++) {
    // тело цикла
  }
  console.log(i) // 3 — i видна за пределами цикла!
}

Это приводило к классической проблеме замыканий в цикле:

// Проблема: все коллбэки видят одну и ту же i
var fns = []
for (var i = 0; i < 3; i++) {
  fns.push(function() { return i })
}
console.log(fns[0]()) // 3, а не 0!
console.log(fns[1]()) // 3
console.log(fns[2]()) // 3
Замыкания

let и const — блочный скоуп

let и const создают переменные с блочным скоупом: переменная видна только внутри блока {}, где объявлена.

function example() {
  if (true) {
    let x = 10  // видна только внутри if
    const y = 20
  }
  // console.log(x) // ReferenceError
  // console.log(y) // ReferenceError

  for (let i = 0; i < 3; i++) {
    // каждая итерация — свой i
  }
  // console.log(i) // ReferenceError
}

Решение проблемы с циклом — просто заменить var на let:

// let: каждая итерация создаёт новую переменную i
const fns = []
for (let i = 0; i < 3; i++) {
  fns.push(() => i)
}
console.log(fns[0]()) // 0 ✓
console.log(fns[1]()) // 1 ✓
console.log(fns[2]()) // 2 ✓

IIFE — паттерн до ES6

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

// Синтаксис IIFE
(function() {
  // код здесь изолирован от внешнего скоупа
  var privateVar = 'я приватный'
})()

// Вариант со стрелочной функцией (ES6+)
(() => {
  // тоже IIFE
})()

До появления let/const и модулей ES6, IIFE использовался для:

1. Создания приватного состояния

// Модуль через IIFE (паттерн до ES6 модулей)
const counter = (function() {
  let count = 0  // приватная переменная

  return {
    increment() { return ++count },
    decrement() { return --count },
    getCount() { return count }
  }
})()

counter.increment() // 1
counter.increment() // 2
// count недоступен снаружи!

2. Изоляции библиотек

// jQuery-стиль: изоляция от глобального скоупа
(function($, window) {
  // здесь $ — это jQuery, изолированно
})(jQuery, window)

3. Решения проблемы var в цикле (до let)

// До ES6: IIFE создавал новый скоуп для каждой итерации
var fns = []
for (var i = 0; i < 3; i++) {
  (function(j) {
    fns.push(function() { return j })
  })(i)  // i передаётся как j — новая переменная каждый раз
}
console.log(fns[0]()) // 0 ✓

Когда сегодня нужен IIFE

В современном JavaScript (ES6+) IIFE нужен редко — модули и let/const решают большинство задач. Но он ещё встречается:

// Асинхронный IIFE для top-level await (до ES2022)
(async () => {
  const data = await fetchData()
  console.log(data)
})()

// Сложная инициализация с промежуточными переменными
const result = (() => {
  const a = heavyComputation()
  const b = anotherComputation(a)
  return combine(a, b)
})()
**Почему (function() {})() работает?**

Обычно function foo() {} — это объявление функции (statement). Чтобы вызвать функцию немедленно, нужно превратить её в выражение (expression). Скобки вокруг функции делают именно это: говорят парсеру «это выражение, не объявление». После этого () вызывают полученное выражение.

Есть и другие способы сделать функцию выражением: !function() {}(), +function() {}(), void function() {}() — все они тоже создают IIFE, но () — самый читаемый.

Практическое руководство

СитуацияРешение
Нужна переменная только в блокеlet/const
Проблема var в циклеЗаменить на let
Нужен приватный модульный скоупES6 модули или let/const
Legacy-код с varПонимай hoisting, мигрируй на let/const
Top-level asyncasync IIFE или Top-level await (ES2022)