← Назад к вопросам

Что такое замыкание со стороны функции и переменных?

1.3 Junior🔥 231 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Замыкания: функция + переменные

Это один из самых важных и часто неправильно понимаемых концептов в JavaScript. После 10+ лет работы скажу: правильное понимание замыканий решает 70% проблем с багами в коде.

Определение замыкания

Замыкание — это функция вместе с переменными из её лексического окружения. Проще: функция, которая "помнит" переменные из области, где она была определена.

function outer() {
  const message = "Hello"; // переменная из внешней области
  
  function inner() {
    console.log(message); // inner помнит message
  }
  
  return inner; // возвращаем функцию вместе с её окружением (замыкание)
}

const closure = outer();
closure(); // Hello

// closure — это замыкание (функция + переменные из outer)

Как работают замыкания

Когда функция создаётся, она запоминает свой лексический контекст:

const globalVar = "global";

function createClosures() {
  const outerVar = "outer";
  
  function closure1() {
    const innerVar = "inner";
    return [globalVar, outerVar, innerVar]; // помнит всё
  }
  
  function closure2() {
    return [globalVar, outerVar]; // помнит outer и global
  }
  
  return { closure1, closure2 };
}

const { closure1, closure2 } = createClosures();

console.log(closure1()); // ["global", "outer", "inner"]
console.log(closure2()); // ["global", "outer"]

// Важно: каждое замыкание имеет доступ к переменным из своего окружения

Функциональная сторона замыканий

Замыкание работает как упакованная функция с частью контекста:

function makeMultiplier(multiplier) {
  // multiplier упакован в замыкание
  return function(number) {
    return number * multiplier;
  };
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

// Функция + переменная multiplier = замыкание
// У каждого замыкания свой multiplier

Функция видит:

  1. Свои локальные переменные
  2. Параметры функции
  3. Переменные из родительских функций
  4. Глобальные переменные
const globalX = "global";

function outer(paramY) { // paramY — часть окружения
  const localZ = "local"; // localZ — часть окружения
  
  return function inner(paramA) {
    // inner имеет доступ к:
    // 1. paramA (собственный параметр)
    // 2. localZ (переменная из outer)
    // 3. paramY (параметр из outer)
    // 4. globalX (глобальная переменная)
    
    return [paramA, localZ, paramY, globalX];
  };
}

const closure = outer("Y value");
console.log(closure("A value"));
// ["A value", "local", "Y value", "global"]

Переменные в замыканиях

Ключевой момент: замыкание сохраняет ссылку на переменную, не её значение:

function createClosures() {
  let shared = 0;
  
  return {
    incrementer: function() {
      return ++shared; // изменяет одну переменную shared
    },
    decrementer: function() {
      return --shared; // изменяет ту же переменную
    }
  };
}

const { incrementer, decrementer } = createClosures();

console.log(incrementer()); // 1
console.log(incrementer()); // 2
console.log(decrementer()); // 1
console.log(decrementer()); // 0

// Оба замыкания помнят одну и ту же переменную shared
// Другой пример: каждое замыкание помнит свою переменную
const closures = [];

for (let i = 0; i < 3; i++) {
  const value = i; // каждое замыкание помнит свой value
  closures.push(() => value);
}

console.log(closures[0]()); // 0
console.log(closures[1]()); // 1
console.log(closures[2]()); // 2

// Каждое замыкание имеет свой value из своего цикла

Практические паттерны с замыканиями

1. Data Privacy (инкапсуляция)

function createBankAccount(initialBalance) {
  let balance = initialBalance; // приватная переменная
  
  return {
    deposit: function(amount) {
      balance += amount;
      return balance;
    },
    withdraw: function(amount) {
      balance -= amount;
      return balance;
    },
    getBalance: function() {
      return balance;
    }
  };
}

const account = createBankAccount(100);
console.log(account.deposit(50));    // 150
console.log(account.withdraw(30));   // 120
console.log(account.getBalance());   // 120

// Нельзя получить доступ к balance напрямую
console.log(account.balance); // undefined

2. Factory Function

function createUser(name, email) {
  const createdAt = new Date();
  
  return {
    getName: () => name,
    getEmail: () => email,
    getCreatedAt: () => createdAt,
    updateEmail: (newEmail) => {
      // email защищена, изменить можно только через метод
      if (newEmail.includes("@")) {
        email = newEmail;
        return true;
      }
      return false;
    }
  };
}

const user = createUser("John", "john@example.com");
console.log(user.getName()); // John
user.updateEmail("john2@example.com");
console.log(user.getEmail()); // john2@example.com

3. Callback с параметром

function setupButtons(buttons) {
  buttons.forEach((buttonEl, index) => {
    buttonEl.addEventListener("click", function() {
      // Замыкание помнит index
      console.log(`Button ${index} clicked`);
    });
  });
}

const buttons = document.querySelectorAll("button");
setupButtons(buttons);

// Каждое замыкание помнит свой index
// Это работает благодаря замыканиям

4. Currying (частичное применение)

function add(a) {
  return function(b) {
    return a + b; // помнит a
  };
}

const add5 = add(5); // замыкание с a=5
console.log(add5(3)); // 8
console.log(add5(10)); // 15

// Или через стрелочные функции
const multiply = (a) => (b) => a * b;
const double = multiply(2);
console.log(double(5)); // 10

5. Memoization (кеширование результатов)

function memoize(fn) {
  const cache = {}; // кеш в замыкании
  
  return function(...args) {
    const key = JSON.stringify(args);
    
    if (key in cache) {
      console.log("From cache:", key);
      return cache[key];
    }
    
    console.log("Computing:", key);
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

const expensiveFn = memoize((n) => {
  // Дорогое вычисление
  return n * 2;
});

console.log(expensiveFn(5));  // Computing: [5] → 10
console.log(expensiveFn(5));  // From cache: [5]
console.log(expensiveFn(10)); // Computing: [10] → 20

Замыкания в React

// useCallback создаёт замыкание с зависимостями
function Counter() {
  const [count, setCount] = useState(0);
  
  // increment — замыкание, помнит setCount и других зависимостей
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []); // пустой массив = зависимостей нет
  
  return <button onClick={increment}>{count}</button>;
}

// Event listener — тоже замыкание
function Component() {
  const [message, setMessage] = useState("Hello");
  
  useEffect(() => {
    const handleClick = () => {
      console.log(message); // замыкание помнит message
    };
    
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
  }, [message]); // message в зависимостях
}

Проблемы с замыканиями

Утечки памяти

// ❌ Проблема: замыкание помнит весь объект
function createHeavyObject() {
  const hugeObject = { data: new Array(1000000).fill("data") };
  
  return function() {
    console.log(hugeObject.data[0]); // помнит весь объект
  };
}

const closure = createHeavyObject();
// hugeObject не будет собран сборщиком мусора
// пока существует closure

// ✅ Правильно: сохраняй только нужное
function createEfficientObject() {
  const hugeObject = { data: new Array(1000000).fill("data") };
  const firstItem = hugeObject.data[0]; // сохраняем только нужное
  
  return function() {
    console.log(firstItem);
  };
}

Проблема с циклом (классическая)

// ❌ Проблема: var не создаёт новую область
var callbacks = [];
for (var i = 0; i < 3; i++) {
  callbacks.push(function() {
    return i; // все помнят одну i
  });
}

console.log(callbacks[0]()); // 3
console.log(callbacks[1]()); // 3

// ✅ Решение: let создаёт новую область для каждой итерации
const callbacks = [];
for (let i = 0; i < 3; i++) {
  callbacks.push(function() {
    return i; // каждое помнит свой i
  });
}

console.log(callbacks[0]()); // 0
console.log(callbacks[1]()); // 1

Вывод

Замыкание — это функция + переменные из её окружения. Ключевые моменты:

  1. Функциональная часть: код функции
  2. Переменная часть: доступ к переменным из лексического окружения
  3. Сохранение: замыкание сохраняет ссылку на переменные, не их значения
  4. Каждое замыкание имеет собственный набор переменных
  5. Практическое применение: инкапсуляция, кеширование, callbacks, currying

Понимание замыканий критично для написания чистого и эффективного JavaScript кода.

Что такое замыкание со стороны функции и переменных? | PrepBro