Что такое замыкание (closure) в JavaScript?

Ответ

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

Практический пример:

function createCounter() {
  let count = 0; // Приватная переменная, инкапсулированная замыканием

  return {
    increment: function() {
      count += 1;
      return count;
    },
    decrement: function() {
      count -= 1;
      return count;
    },
    getValue: function() {
      return count;
    }
  };
}

const myCounter = createCounter();
console.log(myCounter.getValue()); // 0
console.log(myCounter.increment()); // 1
console.log(myCounter.increment()); // 2
console.log(myCounter.decrement()); // 1
// Переменная `count` напрямую недоступна извне, защищена замыканием.

Ключевые аспекты и применение:

  1. Создание приватного состояния: Как показано в примере выше, замыкание позволяет эмулировать приватные поля, скрывая данные от глобальной области видимости.
  2. Фабрики функций и каррирование: Позволяет создавать специализированные функции на основе общего шаблона.
    function multiplier(factor) {
      return function(x) {
        return x * factor;
      };
    }
    const double = multiplier(2);
    const triple = multiplier(3);
    console.log(double(5)); // 10
    console.log(triple(5)); // 15
  3. Работа с асинхронным кодом и обработчиками событий: Частая причина появления замыканий — сохранение контекста переменной внутри колбэка.
    function setupButtons() {
      for (var i = 0; i < 3; i++) {
        // Использование let вместо var решает классическую проблему,
        // так как let создаёт новую привязку для каждой итерации.
        let buttonId = i;
        document.getElementById(`btn-${i}`).addEventListener('click', function() {
          console.log(`Нажата кнопка ${buttonId}`); // Замыкание захватывает buttonId
        });
      }
    }

Важное замечание: Замыкание хранит ссылку на внешнюю переменную, а не её значение на момент создания. Это может приводить к неочевидному поведению в циклах (с var, как в старом примере выше) и требует внимания к управлению памятью, так как ссылки могут препятствовать сборке мусора.

Ответ 18+ 🔞

Да ты посмотри, что эти ваши замыкания вытворяют! Ну реально, хитрая жопа, а не концепция. Сидит функция, такая невинная с виду, а сама-то всё помнит, блядь! Помнит, где родилась, какие переменные вокруг были — хоть её родительскую функцию уже давно в утиль отправили.

Вот смотри, как это работает, ёпта:

function createCounter() {
  let count = 0; // Эта переменная — наша потайная комнатушка, спрятанная замыканием

  return {
    increment: function() {
      count += 1;
      return count;
    },
    decrement: function() {
      count -= 1;
      return count;
    },
    getValue: function() {
      return count;
    }
  };
}

const myCounter = createCounter();
console.log(myCounter.getValue()); // 0
console.log(myCounter.increment()); // 1
console.log(myCounter.increment()); // 2
console.log(myCounter.decrement()); // 1
// Попробуй-ка сюда дотянуться до `count` напрямую — нихуя не выйдет! Она как в сейфе.

И где эта хуйня пригождается:

  1. Спрятать концы в воду. То есть сделать приватное состояние, как в примере. Снаружи никто к твоему count не подберётся, а функции-методы, которые ты вернул, они ж там были рождены, они всё видят и помнят. Красота, ебать копать!

  2. Фабрика функций. Сделал один раз, а потом штампуй, как горячие пирожки.

    function multiplier(factor) {
      return function(x) {
        return x * factor; // А factor-то кто? А это наш старый знакомый из внешней функции!
      };
    }
    const double = multiplier(2); // Запомнила цифру 2 и сидит с ней
    const triple = multiplier(3); // А эта — с тройкой
    console.log(double(5)); // 10
    console.log(triple(5)); // 15
  3. Всякие клики и асинхронщина. Тут вообще без замыканий — пиши пропало. Классическая ловушка, когда в цикле навешиваешь обработчики.

    function setupButtons() {
      for (var i = 0; i < 3; i++) {
        // С var была пиздец-история, потому что var одна на весь цикл!
        let buttonId = i; // А вот let — молодец, для каждой итерации свой buttonId создаёт.
        document.getElementById(`btn-${i}`).addEventListener('click', function() {
          console.log(`Нажата кнопка ${buttonId}`); // И каждая функция-обработчик помнит СВОЙ buttonId, спасибо замыканию!
        });
      }
    }

А теперь, чувак, главное предупреждение, чтоб ты не обосрался: Замыкание хранит не копию значения, а живую ссылку на ту самую внешнюю переменную. Это значит, если переменная меняется — все замыкания, которые на неё смотрят, это увидят. И да, из-за этих самых ссылок сборщик мусора иногда руки разводит — не может почистить память, потому что на старые данные кто-то ещё смотрит. Так что не создавай овердохуища ненужных замыканий где попало, а то приложение твоё накроется медным тазом.