Что такое замыкание со стороны функции и переменных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Замыкания: функция + переменные
Это один из самых важных и часто неправильно понимаемых концептов в 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
Функция видит:
- Свои локальные переменные
- Параметры функции
- Переменные из родительских функций
- Глобальные переменные
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
Вывод
Замыкание — это функция + переменные из её окружения. Ключевые моменты:
- Функциональная часть: код функции
- Переменная часть: доступ к переменным из лексического окружения
- Сохранение: замыкание сохраняет ссылку на переменные, не их значения
- Каждое замыкание имеет собственный набор переменных
- Практическое применение: инкапсуляция, кеширование, callbacks, currying
Понимание замыканий критично для написания чистого и эффективного JavaScript кода.