Языки программирования

Замыкания в JavaScript: как они работают и зачем нужны на практике

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

Что такое замыкание на самом деле

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

function createCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

const counter = createCounter();
counter(); // 1
counter(); // 2

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

Где замыкания реально нужны в коде

Как только заменить абстрактный «счётчик» на реальные задачи, становится видно, что замыкания — это рабочая лошадка, а не теоретическая конструкция:

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

Частая ловушка: замыкания внутри циклов

Самый известный баг, связанный с замыканиями, возникает в циклах, когда для переменной цикла используется var:

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// выведет: 3, 3, 3

Причина в том, что var не создаёт отдельную переменную на каждую итерацию — она одна на весь цикл, и все три замыкания ссылаются на одну и ту же ячейку памяти. К моменту, когда сработает первый setTimeout, цикл уже закончился, и i равно 3. Замена var на let решает проблему, потому что let создаёт новую привязку переменной на каждую итерацию цикла — и каждое замыкание получает собственную, независимую копию:

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// выведет: 0, 1, 2
Замыкание — это не отдельный синтаксис, который нужно специально применять, а естественное поведение функций в JavaScript. Понимать его стоит не для того, чтобы «использовать замыкания», а чтобы не удивляться, когда переменная из внешней функции внезапно оказывается общей на несколько вызовов сразу.

На что обратить внимание

У замыканий есть и обратная сторона, о которой реже говорят. Раз замкнутая переменная держится в памяти, пока жива хотя бы одна ссылающаяся на неё функция, долгоживущие замыкания на большие объекты — например, DOM-узлы или большие массивы, случайно захваченные вместе с нужной переменной, — могут мешать сборщику мусора освободить память вовремя. Обычно это не проблема в коротких обработчиках, но становится заметно в длинных сессиях одностраничных приложений, где обработчики событий накапливаются, а старые версии данных никто явно не отписывает.

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

🎬 Короткое видео по теме статьи: «Замыкания в JavaScript» на YouTube Shorts.

← Все статьи
Я люблю Алину Цой (Билялову)