Что такое замыкание (closure)? Объясните на примере

Главная тема почти любого собеседования по JavaScript.

Замыкание (closure) — функция вместе с «запомненным» окружением: она сохраняет доступ к переменным той области, где была создана, даже после того как внешняя функция завершилась.

Суть на примере

Внутренняя функция «видит» переменные внешней. Когда мы возвращаем внутреннюю функцию наружу, она уносит с собой ссылку на эти переменные — они не исчезают, пока жива функция.

function makeCounter() {
  let count = 0;            // приватная переменная
  return function () {
    count++;                // замыкание помнит count
    return count;
  };
}

const counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());

const another = makeCounter(); // своё, независимое окружение
console.log(another());

Вывод:

1
2
3
1

Каждый вызов makeCounter создаёт новое окружение со своим count. Поэтому another начинает с нуля — это и есть «независимые замыкания».

Зачем это нужно: приватные данные

Через замыкание делают приватность: переменную нельзя прочитать снаружи напрямую, только через возвращённые методы. Это паттерн «модуль».

function createBankAccount(start) {
  let balance = start;   // снаружи недоступна
  return {
    deposit(sum) { balance += sum; return balance; },
    getBalance() { return balance; },
  };
}

const acc = createBankAccount(100);
acc.deposit(50);
console.log(acc.getBalance());
console.log(acc.balance); // напрямую — недоступно

Вывод:

150
undefined

Замыкание запоминает аргументы

Часто на собеседовании просят написать функцию, которая «настраивается» аргументом. Это тоже замыкание.

function multiplier(factor) {
  return (n) => n * factor; // factor «запомнен»
}

const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5));
console.log(triple(5));

Вывод:

10
15

Как объяснить замыкание на собеседовании

Чёткая формулировка, которую ценят интервьюеры: «Замыкание — это функция, которая помнит переменные из области, где была объявлена (а не где вызвана). Эти переменные не удаляются сборщиком мусора, пока существует ссылающаяся на них функция». Полезно добавить, что в JavaScript замыкания возникают автоматически — каждая функция замыкается над своим лексическим окружением, отдельный синтаксис для этого не нужен.

Частый дополнительный вопрос — «где замыкания используются на практике?». Хорошие примеры: приватные поля до появления #private в классах, фабрики функций и колбэков, мемоизация (кэш хранится в замкнутой переменной), а также debounce и throttle, которые держат таймер в замыкании. Подвох, о котором стоит упомянуть: замыкание удерживает всё окружение, поэтому ссылка на большой объект внутри долгоживущей функции может стать утечкой памяти.

Итог

  • Замыкание — функция плюс окружение, в котором она создана.
  • Переменные внешней функции живут, пока на них ссылается внутренняя.
  • Замыкания дают приватные данные, счётчики, мемоизацию и «настраиваемые» функции.
Проверьте себя
1. Что такое замыкание?
AФункция без аргументов
BФункция, которая сохраняет доступ к переменным окружения, где была создана
CСпособ закрыть программу
DГлобальная переменная
2. Почему два счётчика из makeCounter() считают независимо?
AОни используют одну глобальную переменную
BКаждый вызов makeCounter создаёт новое окружение со своим count
CЭто случайность
DСчётчики всегда общие
3. Зачем замыкания применяют для приватности?
AОни шифруют данные
BПеременная доступна только изнутри функции, снаружи к ней нет прямого доступа
CОни ускоряют код
DОни делают переменные глобальными
Поддержать проект