Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Замыкания (Closures): Опыт и Практическое Применение
Да, я активно работаю с замыканиями в повседневной разработке. Это один из ключевых концептов JavaScript, без которого невозможна профессиональная работа на фронтенде.
Что такое замыкание?
Замыкание — это функция, которая имеет доступ к переменным из области видимости, в которой она была определена, даже после того как эта область перестала существовать.
// Простой пример
function outer() {
const secretValue = 'секрет'; // переменная внешней функции
function inner() {
console.log(secretValue); // имеет доступ к secretValue
}
return inner;
}
const myClosure = outer();
myClosure(); // Выведет: "секрет"
// Даже хотя outer() уже завершила работу,
// inner() всё ещё имеет доступ к secretValue
Практический опыт: Фабрика функций
Counter Factory — часто встречается в собеседованиях:
function createCounter() {
let count = 0; // приватная переменная
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// count не доступна извне, это приватная переменная
// console.log(counter.count); // undefined
Практическое применение в React:
function useAuth() {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// Замыкание над user, setUser, isLoading
const login = useCallback(async (email: string, password: string) => {
setIsLoading(true);
try {
const response = await api.login(email, password);
setUser(response.data);
} finally {
setIsLoading(false);
}
}, []); // dependencies пусты, но login имеет доступ к setUser благодаря замыканию
const logout = useCallback(() => {
setUser(null);
}, []);
return { user, isLoading, login, logout };
}
Пример 1: Приватные переменные (как в других LanguageS)
const createBank = () => {
let balance = 0; // полностью приватная
return {
deposit: (amount) => {
balance += amount;
return balance;
},
withdraw: (amount) => {
if (amount <= balance) {
balance -= amount;
return balance;
}
throw new Error('Недостаточно средств');
},
getBalance: () => balance,
};
};
const myBank = createBank();
myBank.deposit(1000); // 1000
myBank.withdraw(200); // 800
console.log(myBank.getBalance()); // 800
// Защита от изменения изне
myBank.balance = 9999; // не влияет на реальный balance
console.log(myBank.getBalance()); // всё ещё 800
Пример 2: Функции-обработчики в событиях
// На примере React
function MailList({ emails }: { emails: Email[] }) {
const handleDelete = (emailId: string) => {
// Замыкание над emailId
return async () => {
await api.deleteEmail(emailId);
// emailId доступен в этом замыкании
};
};
return (
<ul>
{emails.map((email) => (
<li key={email.id}>
{email.subject}
<button onClick={handleDelete(email.id)}>
Удалить
</button>
</li>
))}
</ul>
);
}
Пример 3: Debounce и Throttle
Debounce — часто используется для API запросов:
function debounce(func, delay) {
let timeoutId; // переменная в замыкании
return function debounced(...args) {
// debounced имеет доступ к func, delay, timeoutId
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
const debouncedSearch = debounce(async (query) => {
const results = await api.search(query);
// ...
}, 500);
// В input обработчике
input.addEventListener('change', debouncedSearch);
Throttle:
function throttle(func, limit) {
let inThrottle; // переменная в замыкании
return function throttled(...args) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
const throttledScroll = throttle(() => {
console.log('Scroll event');
}, 1000);
window.addEventListener('scroll', throttledScroll);
Пример 4: Event Listeners и Memory Leaks
Это важно для понимания утечек памяти:
function setupComponent(element: HTMLElement) {
let clickCount = 0; // в замыкании
const handleClick = () => {
clickCount++; // имеет доступ к clickCount
console.log(`Клик #${clickCount}`);
};
element.addEventListener('click', handleClick);
// Возвращаем cleanup функцию
return () => {
element.removeEventListener('click', handleClick);
// handleClick удалена, и замыкание тоже может быть удалено
};
}
// Использование
const cleanup = setupComponent(document.querySelector('button'));
// Когда готово — очищаем
cleanup();
Пример 5: Module Pattern
const userModule = (() => {
const users = []; // приватная переменная
return {
add: (user) => {
users.push(user);
},
getAll: () => [...users], // возвращаем копию
remove: (id) => {
const index = users.findIndex((u) => u.id === id);
if (index > -1) users.splice(index, 1);
},
};
})();
userModule.add({ id: 1, name: 'John' });
console.log(userModule.getAll()); // [{id: 1, name: 'John'}]
// users не доступна извне — только через методы
// userModule.users; // undefined
Пример 6: Цепочка вызовов с замыканиями
function add(x) {
return function(y) {
return x + y; // замыкание над x
};
}
const addFive = add(5); // x = 5 в замыкании
console.log(addFive(3)); // 8 (x + y = 5 + 3)
console.log(addFive(10)); // 15 (x + y = 5 + 10)
// Или в одну строку
console.log(add(5)(3)); // 8
Частые ошибки с замыканиями
Ошибка 1: Цикл с setTimeout
// Неправильно
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // выведет 3, 3, 3
}, 1000);
// i в замыкании ссылается на одну и ту же переменную
}
// Правильно #1: let вместо var
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // выведет 0, 1, 2
}, 1000);
// let создаёт новую область видимости для каждой итерации
}
// Правильно #2: IIFE (если нужен var)
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => {
console.log(j); // выведет 0, 1, 2
}, 1000);
})(i);
// Создаём новое замыкание для каждого значения i
}
Ошибка 2: Утечка памяти с замыканиями
// Неправильно
function createLargeObject() {
const largeData = new Array(1000000); // большой объект
return () => {
console.log(largeData.length); // замыкание хранит largeData
};
}
// largeData не может быть удалена из памяти, пока существует замыкание
const callback = createLargeObject();
// Правильно: очищаем, когда не нужно
setTimeout(() => {
callback(); // используем
// потом очищаем
}, 1000);
На собеседовании
Полный ответ должен показать:
- Теория — что такое замыкание и как оно работает
- Практические примеры — где используются в реальном коде
- React примеры — useCallback, custom hooks
- Частые ошибки — что может пойти не так
- Performance — как замыкания влияют на память
- Memory Leaks — как избежать утечек