В предыдущей статье рассматривались области видимости и различия между let, const и var. Эти темы напрямую приводят к замыканиям. Замыкание — не отдельный механизм и не «продвинутая техника», а естественное следствие того, как JavaScript работает с функциями и переменными.

Если кратко, замыкание возникает тогда, когда функция продолжает использовать переменные из внешней области после того, как эта внешняя область перестала выполняться.

Отправная точка: локальная переменная

function calculateTotal(price) {
  const tax = 0.2;
  return price + price * tax;
}

calculateTotal(100);

Здесь:

  • tax создаётся при вызове функции
  • используется внутри неё
  • после завершения функции больше не существует

Это обычное поведение локальных переменных.

Вложенная функция и расширение области доступа

function createCalculator() {
  const tax = 0.2;

  function calculate(price) {
    return price + price * tax;
  }

  return calculate;
}

const calculateTotal = createCalculator();
calculateTotal(100);

Этот код выглядит похожим, но ведёт себя принципиально иначе.

Функция createCalculator завершила выполнение, но:

  • функция calculate продолжает существовать
  • она использует переменную tax
  • значит, JavaScript сохраняет эту переменную в памяти

Почему JavaScript сохраняет переменные

JavaScript не «удерживает» переменные специально.
Он просто следует простому правилу: данные не удаляются, пока на них есть ссылка.

В данном случае:

  • ссылка есть у функции calculate
  • поэтому tax остаётся доступной

Это и есть замыкание.

Что такое замыкание без формальных определений

На практике можно считать, что:

  • функция «запоминает» переменные, которые были доступны при её создании
  • эти переменные становятся частью её внутреннего состояния

Это не копия значений и не снимок состояния, а живая связь с переменными.

Замыкание как способ хранения состояния

Рассмотрим задачу, которая регулярно встречается в коде.

Нужно:

  • один раз задать параметры
  • использовать их много раз
  • не хранить их в глобальной области
function createUserFormatter() {
  const prefix = 'User: ';

  return function (name) {
    return prefix + name;
  };
}

const formatUser = createUserFormatter();
formatUser('Alex');
formatUser('Maria');

prefix:

  • создаётся один раз
  • не доступен напрямую
  • используется при каждом вызове функции

Это состояние, сохранённое через замыкание.

Почему не использовать глобальные переменные

Тот же код без замыкания:

const prefix = 'User: ';

function formatUser(name) {
  return prefix + name;
}

Формально код работает, но:

  • prefix доступен отовсюду
  • его легко переопределить
  • поведение функции зависит от внешнего окружения

Замыкание делает поведение функции более предсказуемым.

Несколько независимых состояний

const adminFormatter = createUserFormatter();
const guestFormatter = createUserFormatter();

Каждый вызов createUserFormatter:

  • создаёт собственную область
  • собственный prefix
  • независимое состояние

Это невозможно реализовать корректно с глобальными переменными.

Ошибка, которая выглядит странно без понимания замыканий

const loggers = [];

for (var i = 1; i <= 3; i++) {
  loggers.push(function () {
    console.log('Item', i);
  });
}

На первый взгляд кажется, что каждая функция должна помнить своё значение i.
Но все функции замыкаются на одну и ту же переменную.

Решение уже известно:

for (let i = 1; i <= 3; i++) {
  loggers.push(function () {
    console.log('Item', i);
  });
}

Теперь каждая итерация создаёт новую переменную, и замыкания работают ожидаемо.

Где замыкания используются в реальном коде

Замыкания лежат в основе:

  • модулей
  • фабрик функций
  • конфигурации
  • инкапсуляции данных
  • обработчиков событий

Пример, который легко узнать:

function createConfig() {
  const env = 'production';

  return {
    getEnv() {
      return env;
    }
  };
}

Итог статьи

На этом этапе важно зафиксировать несколько идей:

  1. Замыкания — следствие областей видимости
  2. Функции продолжают иметь доступ к внешним переменным
  3. Переменные живут, пока используются
  4. Замыкания позволяют хранить состояние и скрывать данные

В следующей статье будет рассмотрено, как JavaScript работает с типами данных, потому что именно типы определяют, какое состояние хранится и как оно может изменяться.