Перейти к содержанию
Innovialabs

Что такое замыкания в JavaScript: объяснение на примерах

Основы3 мин

Коротко

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

Что такое замыкание

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

function makeGreeting(name) {
  return function () {
    return `Привет, ${name}!`;
  };
}

const hiAnna = makeGreeting('Анна');
hiAnna(); // "Привет, Анна!"

makeGreeting давно завершилась, но внутренняя функция всё ещё видит name. Это и есть замыкание.

Лексическая область видимости

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

const color = 'синий';

function show() {
  console.log(color);
}

function run() {
  const color = 'красный';
  show();
}

run(); // "синий"

show объявлена на верхнем уровне, поэтому видит внешний color, а не тот, что внутри run.

Поиск переменной идёт по цепочке: сначала текущая функция, затем внешняя, и так до глобальной области.

Практическое применение

Счётчик

function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    get: () => count,
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
counter.get(); // 2

Каждый вызов createCounter создаёт свою переменную count. Два счётчика не мешают друг другу.

Приватное состояние

Снаружи до count не добраться — только через методы. Это способ скрыть данные без классов. Похожим образом работают модули, хуки в React (useState опирается на замыкания) и обработчики событий.

Мемоизация

Замыкание может хранить кэш результатов:

function memoize(fn) {
  const cache = new Map();
  return (n) => {
    if (cache.has(n)) return cache.get(n);
    const result = fn(n);
    cache.set(n, result);
    return result;
  };
}

const slowSquare = (n) => n * n;
const fastSquare = memoize(slowSquare);

cache живёт внутри замыкания и сохраняется между вызовами.

Классический баг: var в цикле

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 3, 3, 3

Почему не 0, 1, 2? У var функциональная область видимости: на весь цикл одна переменная i. К моменту запуска таймеров цикл уже закончился, и i равна 3. Все три функции замкнули одну и ту же переменную.

Решение — let. У него блочная область видимости, и на каждую итерацию создаётся новая i:

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 0, 1, 2

Частые ошибки

  • Ожидать копию значения. Замыкание хранит ссылку на переменную, а не её значение в момент создания. Если переменная изменится, функция увидит новое значение.
  • Устаревшие значения в React. Обработчик, созданный на одном рендере, видит state этого рендера. Отсюда «stale closure» в useEffect и setInterval.
  • Утечки памяти. Пока замыкание живо, живут и переменные, на которые оно ссылается. Снимайте ненужные обработчики событий и таймеры.

Частые вопросы

Технически да: любая функция запоминает окружение, в котором создана. Но обычно словом «замыкание» называют случай, когда функция используется вне этого окружения и продолжает обращаться к его переменным.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.