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

Ответ

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

Простой пример:

function createCounter() {
  let count = 0; // `count` — переменная в лексическом окружении createCounter

  // Функция increment является замыканием
  return function increment() {
    count += 1; // Она имеет доступ к `count` даже после завершения createCounter
    return count;
  };
}

const myCounter = createCounter(); // createCounter завершилась, но её окружение «живёт» в замыкании
console.log(myCounter()); // 1
console.log(myCounter()); // 2
console.log(myCounter()); // 3
// У каждого вызова createCounter() — своё независимое замыкание с отдельной `count`.

Практическое применение в Node.js:

  1. Создание приватных переменных и инкапсуляция:

    function createDatabaseConnection(config) {
      const connectionString = config.url; // «Приватная» переменная
      return {
        connect() { console.log(`Connecting to ${connectionString}`); },
        query(sql) { /* использует connectionString */ }
      };
    }
    const db = createDatabaseConnection({ url: 'localhost:5432' });
    // Нет прямого доступа к `connectionString` извне, только через методы.
  2. Фабрики и middleware: Многие библиотеки Express используют замыкания для создания конфигурируемых middleware.

    function logger(format) {
      return function(req, res, next) { // Middleware — замыкание, помнящее `format`
        console.log(format, req.method, req.url);
        next();
      };
    }
    app.use(logger(':method :url'));

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

Ответ 18+ 🔞

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

Вот представь: есть у тебя функция, обычная такая. Она создаёт внутри какую-нибудь переменную, ну, let count = 0. Потом она возвращает другую функцию, которая эту переменную использует. И вот магия, блядь: внешняя функция уже давно отработала, её контекст вроде бы должен был сдохнуть и уйти в небытие, но нет! Внутренняя-то функция, которую ты получил, как будто замыкает на себе это самое лексическое окружение — все эти переменные, что были вокруг неё в момент рождения. Она их помнит, она к ним имеет доступ. Это и есть замыкание, ебать копать. Не функция сама по себе, а эта связка — функция плюс её запомненная область видимости.

function createCounter() {
  let count = 0; // Эта штука живёт в замыкании

  return function increment() {
    count += 1; // А эта внутренняя функция всё про неё помнит!
    return count;
  };
}

const myCounter = createCounter(); // Функция createCounter умерла, да здравствует замыкание!
console.log(myCounter()); // 1
console.log(myCounter()); // 2
// Каждый вызов myCounter лезет в то самое запомненное окружение и меняет ту самую count.

Практическая польза? Да овердохуища! Самый частый кейс — спрятать данные, сделать что-то вроде приватного поля. Смотри, как хитрая жопа может сработать:

function createDatabaseConnection(config) {
  const password = config.pass; // Вот эта привата, её снаружи не достать
  return {
    connect() { console.log(`Connecting with pass: ****`); /* тут есть доступ к password */ },
    // А вот так напрямую — хуй получится. Доверия к внешнему миру — ноль ебать.
  };
}
const db = createDatabaseConnection({ pass: 'qwerty123' });
// Попробуй снаружи получить password — нихуя не выйдет, только через методы объекта.

В Express.js на этом вообще пол-архитектуры стоит. Middleware фабрики — это чистые замыкания.

function logger(format) { // format запомнится
  return function(req, res, next) { // А это уже сам middleware
    console.log(format, req.url); // И тут он этот format использует
    next();
  };
}
app.use(logger('[LOG]')); // Залогировал и в ус не дуешь.

Но есть тут подводный камень, классический, про который все спотыкаются. Замыкание хранит не значение переменной на момент создания, а ссылку на неё. И если ты в цикле нагородил колбэков, которые используют одну и ту же переменную цикла, то получится пиздец. Все колбэки будут тыкать в одно и то же, последнее значение. Раньше, когда был только var, это был ад. Сейчас с let проще — у него блочная область видимости, и на каждой итерации цикла создаётся новая переменная, которую замыкание и захватит. Так что не нарвись на эту грабли, а то будет тебе хиросима, а не код.