Как работают замыкания (Closures) в JavaScript? Объясните на простом примере
function counter() {
let count = 0;
return function() {
count++;
return count;
};
}
const myCounter = counter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
console.log(myCounter()); // 3
Почему переменная count не сбрасывается каждый раз? Ведь функция counter() уже завершила свою работу, а мы всё ещё можем менять её внутреннюю переменную. Я ожидал, что после первого вызова myCounter() переменная count будет уничтожена, так как она объявлена внутри функции.
И ещё вопрос: зачем вообще нужны замыкания? В каких реальных ситуациях они применяются? Буду благодарен за простое объяснение с примерами, желательно на русском. Заранее спасибо!
<<Что такое замыкание и как оно работает?
Замыкание (closure) — это комбинация функции и её лексического окружения (scope), в котором эта функция была объявлена. Простыми словами: функция "запоминает" переменные из внешней функции, даже после того, как внешняя функция завершила выполнение.
Разберём ваш пример по шагам:
function counter() {
let count = 0; // (1) создаётся переменная count
return function() { // (2) возвращаем внутреннюю функцию
count++; // (3) увеличиваем count
return count; // (4) возвращаем новое значение
};
}
const myCounter = counter(); // (5) вызываем counter, получаем внутреннюю функцию
console.log(myCounter()); // 1 // (6) вызываем внутреннюю функцию
console.log(myCounter()); // 2
console.log(myCounter()); // 3
Как это работает под капотом:
- Когда мы вызываем
counter(), JavaScript создаёт локальную переменнуюcountсо значением 0. - Внутренняя функция (анонимная функция) создаётся внутри этого окружения. Она "захватывает" ссылку на переменную
count. - Когда
counter()завершает работу, её локальное окружение обычно должно быть уничтожено. НО! Так как внутренняя функция всё ещё существует (мы сохранили её в переменнуюmyCounter) и ссылается наcount, JavaScript сохраняет это окружение в памяти. - Таким образом,
myCounter— это не просто функция, а замыкание, которое содержит внутри себя и код функции, и "законсервированную" переменнуюcount.
Каждый раз, когда мы вызываем myCounter(), мы обращаемся к той самой переменной count, которая "живёт" внутри замыкания. Поэтому она не сбрасывается, а увеличивается.
Реальные примеры использования замыканий
1. Создание приватных переменных (инкапсуляция)
function createUser(name) {
let password = 'secret123'; // приватная переменная
return {
getName: function() { return name; },
checkPassword: function(pass) { return pass === password; }
};
}
const user = createUser('Иван');
console.log(user.getName()); // 'Иван'
console.log(user.password); // undefined (недоступно снаружи)
console.log(user.checkPassword('secret123')); // true
2. Создание функций с "памятью" (мемоизация)
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. Обработчики событий и асинхронность
function createButtons() {
for (var i = 1; i <= 3; i++) {
(function(index) { // замыкание сохраняет текущее значение i
setTimeout(function() {
console.log('Кнопка ' + index);
}, index * 1000);
})(i);
}
}
createButtons(); // "Кнопка 1", "Кнопка 2", "Кнопка 3" с интервалом
Важно: Если бы мы не использовали замыкание, все setTimeout вывели бы "Кнопка 4", потому что переменная i к моменту выполнения колбэков уже стала бы равна 4.
Замыкания — одна из самых мощных и фундаментальных концепций JavaScript. Они позволяют создавать чистый, модульный и безопасный код. Понимание замыканий откроет вам путь к продвинутым паттернам программирования.
<<Простое объяснение через "рюкзак"
Представьте, что каждая функция — это человек, а замыкание — это рюкзак, который человек носит с собой. Когда функция создаётся, она кладёт в свой рюкзак все переменные, которые видит вокруг себя. Даже если потом внешняя функция "уйдёт", рюкзак остаётся.
В вашем примере: counter() уходит, но её внутренняя функция уносит с собой рюкзак с переменной count. Каждый раз, когда вы вызываете myCounter(), она достаёт из рюкзака count, увеличивает его и кладёт обратно.
Альтернативный подход: использование методов объекта
Замыкания можно заменить методами объекта, но это менее гибко:
const counterObj = {
count: 0,
increment: function() {
this.count++;
return this.count;
}
};
console.log(counterObj.increment()); // 1
console.log(counterObj.increment()); // 2
Проблема этого подхода: переменная count публичная, её можно случайно изменить: counterObj.count = 100. Замыкание же даёт настоящую приватность.
Совет новичкам
Чтобы лучше понять замыкания, попробуйте написать код, в котором одна функция возвращает другую, и поэкспериментируйте в консоли браузера или на JSFiddle. Поставьте debugger внутри и посмотрите, какие переменные доступны на каждом шаге. Практика — лучший способ освоить эту тему.
Ответов пока нет.
Войдите чтобы оставить ответ