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

Построен ли JS на замыканиях

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

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Построен ли JavaScript на замыканиях

Да, JavaScript фундаментально построен на замыканиях. Замыкания - это не просто фича языка, это основа функционального программирования в JavaScript, и практически любой современный код JavaScript использует замыкания.

Что такое замыкание?

Замыкание - это функция, которая "помнит" переменные из своего внешнего (лексического) контекста, даже после того, как этот контекст закончил выполняться.

// Самый простой пример замыкания
function outer() {
  const message = "Hello";  // Переменная в outer scope
  
  function inner() {
    console.log(message);   // inner "помнит" message
  }
  
  return inner;
}

const greeting = outer();
greeting(); // "Hello" - замыкание работает!

В этом примере:

  • outer() заканчивает выполняться
  • Но inner() все еще имеет доступ к message
  • Это и называется замыканием

Почему JavaScript построен на замыканиях?

1. Первое назначение функций

В JavaScript функции - это первоклассные объекты:

// Функция как значение
const greet = function(name) {
  return "Hello, " + name;
};

// Функция в переменной
const sayHi = greet;
sayHi("Alice");  // "Hello, Alice"

// Функция как аргумент
setTimeout(function() {
  console.log("Delayed!");
}, 1000);

// Функция как возвращаемое значение
const createCounter = () => {
  let count = 0;
  return function() {
    return ++count;
  };
};

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

Без замыканий, возвращаемые функции не смогли бы работать с данными.

2. Отсутствие настоящих "классов" (до ES6)

До ES6 в JavaScript не было классов, поэтому замыкания использовались для инкапсуляции:

// Паттерн Module с замыканиями
const userModule = (function() {
  let users = [];  // Приватная переменная
  
  return {
    addUser: function(name) {
      users.push(name);
    },
    getUsers: function() {
      return users.slice();  // Копия, не оригинал
    }
  };
})();

userModule.addUser("Alice");
userModule.addUser("Bob");
console.log(userModule.getUsers());  // ["Alice", "Bob"]
// Но users недоступна напрямую!

Это был единственный способ создать приватные переменные в JavaScript.

Замыкания везде в JavaScript

Пример 1: forEach и setTimeout

// Замыкание в цикле
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);  // Замыкание! Каждая функция помнит свой i
  }, 1000);
}
// После 1000ms: 0, 1, 2

// (Если бы было var, было бы 3, 3, 3 - классический баг с замыканиями)

Пример 2: Обработчик событий

function setupButtons() {
  for (let id = 1; id <= 3; id++) {
    const button = document.getElementById(`btn-${id}`);
    button.addEventListener('click', function() {
      console.log(`Button ${id} clicked`);  // Замыкание! Помнит id
    });
  }
}

Каждый обработчик события - это замыкание, которое помнит состояние в момент регистрации.

Пример 3: React hooks

// React useState построен на замыканиях!
const [count, setCount] = useState(0);

const increment = () => {
  setCount(count + 1);  // Замыкание! increment помнит count
};

// useEffect тоже замыкание
useEffect(() => {
  console.log(count);  // Замыкание! Помнит текущий count
}, [count]);

Пример 4: Функциональное программирование

// Currying - невозможно без замыканий
const multiply = (a) => (b) => a * b;

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

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

// Каждая returned функция "помнит" свой аргумент a
// Higher-order functions
const withLogging = (fn) => {
  return function(...args) {
    console.log(`Calling ${fn.name}`);
    return fn(...args);  // Замыкание! Помнит fn
  };
};

const add = (a, b) => a + b;
const addWithLog = withLogging(add);
addWithLog(2, 3);  // Logs "Calling add", returns 5

Пример 5: Приватные данные в классах

// До ES2022 приватные поля не было
class Counter {
  constructor() {
    let _count = 0;  // Приватная переменная через замыкание
    
    this.increment = () => ++_count;
    this.get = () => _count;
  }
}

const c = new Counter();
c.increment();  // 1
c.increment();  // 2
c.get();        // 2
c._count;       // undefined - действительно приватная!

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

Проблема 1: Утечка памяти

// Замыкание может удерживать большой объект в памяти
function setupLeak() {
  const hugeData = new Array(1000000).fill('data');
  
  document.getElementById('btn').addEventListener('click', () => {
    console.log(hugeData[0]);  // Замыкание! Удерживает hugeData
  });
  // hugeData никогда не будет garbage collected
}

// Решение: удалить слушатель или очистить ссылку
function setupClean() {
  const hugeData = new Array(1000000).fill('data');
  
  const handler = () => {
    console.log(hugeData[0]);
  };
  
  const button = document.getElementById('btn');
  button.addEventListener('click', handler);
  
  // Когда не нужна
  button.removeEventListener('click', handler);  // Теперь hugeData может быть GC'd
}

Проблема 2: Неправильное значение в цикле (классический баг)

// ПЛОХО - все функции помнят последнее значение i
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function() {
    alert(i);  // Все покажут последнее значение i!
  };
}

// ХОРОШО - каждая функция имеет свой i
for (let i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function() {
    alert(i);  // Каждая "помнит" свой i
  };
}

// ИЛИ - явное замыкание
for (var i = 0; i < buttons.length; i++) {
  (function(index) {
    buttons[index].onclick = function() {
      alert(index);  // Работает!
    };
  })(i);
}

Замыкания и производительность

// Оптимизация: кешируй значения из замыкания
// МЕДЛЕННО
const createCounter = () => {
  const config = { step: 1, start: 0 };  // Берется из замыкания каждый раз
  let count = config.start;
  
  return () => {
    count += config.step;
    return count;
  };
};

// БЫСТРЕЕ
const createCounterFast = (step = 1, start = 0) => {
  let count = start;
  const cachedStep = step;  // Кешируем
  
  return () => {
    count += cachedStep;
    return count;
  };
};

Визуализация замыкания

Основная функция (Scope 1)
  ├─ Переменные: outer_var
  │
  ├─ Вложенная функция (Scope 2) <- Замыкание
  │   ├─ Может читать: outer_var (из Scope 1)
  │   ├─ Может писать: outer_var (из Scope 1)
  │   └─ Может создавать: inner_var
  │
  └─ Когда основная функция закончится
       Вложенная функция все еще помнит outer_var

Заключение

JavaScript построен на замыканиях потому что:

  1. Функции - первоклассные объекты - функции могут возвращать функции и помнить контекст

  2. Отсутствие классов (исторически) - замыкания были единственным способом инкапсуляции

  3. Асинхронное программирование - обработчики событий, таймеры, callbacks - все используют замыкания

  4. Функциональное программирование - currying, HOF, мемоизация - все на замыканиях

  5. Гибкость - замыкания позволяют создавать мощные абстракции и паттерны

Понимание замыканий - это ключ к пониманию JavaScript. Это не просто фича, это фундамент языка.