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

Как работает механизм замыкания?

2.2 Middle🔥 211 комментариев
#JavaScript Core

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

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

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

Как работает механизм замыкания?

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

Основной концепт

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

function outer() {
  let count = 0; // Переменная во внешней области видимости
  
  function inner() {
    count++; // Доступ к переменной outer
    return count;
  }
  
  return inner;
}

const counter = outer(); // inner функция "запомнила" count переменную
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

Как это работает?

1. Когда функция outer вызывается:

  • Создаётся контекст выполнения (execution context)
  • В памяти появляется переменная count
  • Функция inner "запомнила" ссылку на count

2. Функция inner возвращается:

  • Контекст outer закрывается
  • count НЕ удаляется из памяти (потому что inner её использует)
  • Это и есть замыкание — inner "закрыла" (closed) count

3. Когда inner вызывается позже:

  • Функция всё ещё имеет доступ к переменной count
  • count остаётся в памяти между вызовами

Области видимости в JavaScript

// Global scope
let globalVar = 'Глобальная';

function outer() {
  // Outer function scope
  let outerVar = 'Внешняя';
  
  function middle() {
    // Middle function scope
    let middleVar = 'Средняя';
    
    function inner() {
      // Inner function scope
      let innerVar = 'Внутренняя';
      
      // Имеет доступ ко всем переменным выше
      console.log(innerVar);    // 'Внутренняя' ✓
      console.log(middleVar);   // 'Средняя' ✓
      console.log(outerVar);    // 'Внешняя' ✓
      console.log(globalVar);   // 'Глобальная' ✓
    }
    
    return inner;
  }
  
  return middle();
}

const func = outer();
func(); // Выведет все значения благодаря замыканию

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

1. Приватные переменные (инкапсуляция)

function createUser(name) {
  // Приватные переменные
  let balance = 0;
  let transactions = [];
  
  // Публичные методы (замыкания)
  return {
    getName: () => name,
    getBalance: () => balance,
    deposit: (amount) => {
      balance += amount;
      transactions.push({ type: 'deposit', amount });
      return balance;
    },
    withdraw: (amount) => {
      if (amount <= balance) {
        balance -= amount;
        transactions.push({ type: 'withdraw', amount });
        return balance;
      }
      return 'Недостаточно средств';
    },
    getTransactions: () => transactions
  };
}

const user = createUser('John');
console.log(user.getBalance());     // 0
console.log(user.deposit(100));     // 100
console.log(user.withdraw(30));     // 70
console.log(user.getTransactions()); // История всех операций
// console.log(user.balance); // undefined (приватно)

2. Фабрика функций (Function Factory)

function createMultiplier(multiplier) {
  // Замыкание запоминает multiplier
  return function(number) {
    return number * multiplier;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

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

// Каждая функция имеет свой multiplier благодаря замыканию

3. Кэширование результатов (Memoization)

function memoize(fn) {
  const cache = {}; // Приватный кэш
  
  return function(arg) {
    if (arg in cache) {
      console.log('Из кэша');
      return cache[arg];
    }
    
    console.log('Вычисляю...');
    const result = fn(arg);
    cache[arg] = result;
    return result;
  };
}

const expensiveFunction = memoize((n) => {
  // Дорогая операция
  return n * n * n;
});

console.log(expensiveFunction(5)); // Вычисляю... => 125
console.log(expensiveFunction(5)); // Из кэша => 125
console.log(expensiveFunction(10)); // Вычисляю... => 1000

4. Обработчики событий с параметрами

const buttons = document.querySelectorAll('button');

for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', function(e) {
    // ❌ ПЛОХО (без замыкания) — вывести неправильный index
    // console.log('Кнопка ' + i); // Всегда последний i!
  });
}

// ✅ ХОРОШО (с замыканием) — правильный index
for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', (function(index) {
    return function(e) {
      console.log('Кнопка ' + index); // Правильный index благодаря замыканию
    };
  })(i));
}

// Или с let (современный способ)
for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', function(e) {
    console.log('Кнопка ' + i); // Каждый let i имеет свою область видимости
  });
}

5. Модульный паттерн (Module Pattern)

const Calculator = (function() {
  // Приватные переменные и функции
  let lastResult = 0;
  
  function log(operation, result) {
    console.log(`${operation} = ${result}`);
    lastResult = result;
  }
  
  // Публичный интерфейс
  return {
    add: (a, b) => {
      const result = a + b;
      log('add', result);
      return result;
    },
    multiply: (a, b) => {
      const result = a * b;
      log('multiply', result);
      return result;
    },
    getLastResult: () => lastResult
  };
})();

Calculator.add(5, 3);        // add = 8
Calculator.multiply(4, 2);   // multiply = 8
console.log(Calculator.getLastResult()); // 8

6. Декоратор функции (Function Decorator)

function withLogging(fn) {
  return function(...args) {
    console.log(`Вызов функции с аргументами: ${args}`);
    const result = fn(...args);
    console.log(`Результат: ${result}`);
    return result;
  };
}

const add = (a, b) => a + b;
const addWithLogging = withLogging(add);

addWithLogging(5, 3);
// Вызов функции с аргументами: 5,3
// Результат: 8

Частые ошибки с замыканиями

Ошибка 1: Утечка памяти

function createLargeObject() {
  let largeArray = new Array(1000000); // Большой массив
  
  return function() {
    console.log('Готово');
  };
}

const func = createLargeObject();
// largeArray остаётся в памяти, хотя не используется!

Ошибка 2: Неожиданная переменная в цикле

// ❌ ПЛОХО
const funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function() {
    return i; // Все функции вернут одно и то же i!
  });
}
console.log(funcs[0]()); // 3
console.log(funcs[1]()); // 3
console.log(funcs[2]()); // 3

// ✅ ХОРОШО
const funcs = [];
for (let i = 0; i < 3; i++) { // let вместо var!
  funcs.push(function() {
    return i; // Каждый i имеет свою область видимости
  });
}
console.log(funcs[0]()); // 0
console.log(funcs[1]()); // 1
console.log(funcs[2]()); // 2

Как браузер управляет замыканиями

  1. Garbage Collection не удаляет переменные, которые используются в замыканиях
  2. DevTools показывают замыкания в Scope
  3. Performance - замыкания требуют дополнительную память

Ключевые моменты

  • Замыкание — функция с доступом к переменным внешней области
  • Создаётся автоматически при вложенных функциях
  • Позволяет создавать приватные переменные
  • Используется в модульном паттерне, декораторах, кэшировании
  • Замыкания требуют памяти (переменные не удаляются)
  • В циклах используй let для правильного замыкания
Как работает механизм замыкания? | PrepBro