Замыкания и область видимости в JavaScript простыми словами
Вы пишете функцию JavaScript, которая возвращает другую функцию. Внутренняя функция использует переменную из внешней функции, но когда вы вызываете её позже, внешняя функция уже завершила выполнение. И всё же переменная всё ещё существует. Как? Это магия замыканий и области видимости. Многие разработчики сталкиваются с трудностями при понимании этой концепции, что приводит к ошибкам, таким как неожиданные значения переменных или утечки памяти. В этой статье мы разберём замыкания и область видимости JavaScript простыми словами на практических примерах.
Что такое область видимости в JavaScript?
Область видимости определяет, где переменные доступны в вашем коде. В JavaScript есть три основных типа области видимости:
- Глобальная область видимости: Переменные, объявленные вне любой функции или блока, доступны глобально.
- Функциональная область видимости: Переменные, объявленные внутри функции, доступны только внутри этой функции.
- Блочная область видимости: Переменные, объявленные с помощью
letиconstвнутри блока{}, доступны только внутри этого блока.
Вот быстрый пример:
let globalVar = 'I am global';
function myFunc() {
let functionVar = 'I am in function scope';
if (true) {
let blockVar = 'I am in block scope';
console.log(globalVar); // accessible
console.log(functionVar); // accessible
console.log(blockVar); // accessible
}
console.log(blockVar); // ReferenceError: blockVar is not defined
}
myFunc();
console.log(functionVar); // ReferenceError: functionVar is not defined
Понимание области видимости имеет решающее значение, поскольку оно предотвращает конфликты переменных и помогает управлять памятью.
Лексическая область видимости: основа замыканий
JavaScript использует лексическую область видимости, что означает, что область видимости переменной определяется её положением в исходном коде. Внутренние функции имеют доступ к переменным во внешней (охватывающей) области видимости. Это также называется статической областью видимости.
Рассмотрим это:
function outer() {
let outerVar = 'outer';
function inner() {
console.log(outerVar); // 'outer'
}
inner();
}
outer();
Функция inner может получить доступ к outerVar, потому что она лексически находится внутри outer. Это не динамическая область видимости; она не зависит от того, как вызывается функция.
Что такое замыкание?
Замыкание — это функция, которая запоминает свою лексическую область видимости, даже когда функция выполняется вне этой области. Другими словами, замыкание даёт вам доступ к области видимости внешней функции из внутренней функции. Замыкания создаются каждый раз при создании функции, в момент создания функции.
Пример:
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
Здесь makeCounter возвращает внутреннюю функцию, которая увеличивает count. Даже после того, как makeCounter завершил выполнение, возвращённая функция всё ещё имеет доступ к count. Это и есть замыкание.
Как работают замыкания под капотом
Когда функция создаётся, она сохраняет ссылку на своё лексическое окружение (цепочку областей видимости). Когда функция вызывается, она использует это окружение для разрешения переменных. Окружение сохраняется, пока существует замыкание.
Практическое применение замыканий
Замыкания — это не просто теория; они используются повсюду в JavaScript.
1. Приватность данных (модульный паттерн)
До появления модулей ES6 замыкания использовались для создания приватных переменных:
const counterModule = (function() {
let count = 0;
return {
increment: function() { count++; },
getCount: function() { return count; }
};
})();
counterModule.increment();
console.log(counterModule.getCount()); // 1
console.log(counterModule.count); // undefined
Здесь count является приватным и доступен только через возвращённые методы.
2. Обработчики событий и колбэки
Замыкания обычно используются в обработчиках событий для доступа к переменным из внешней области видимости:
function setupButton() {
let clickCount = 0;
document.getElementById('myButton').addEventListener('click', function() {
clickCount++;
console.log(`Button clicked ${clickCount} times`);
});
}
Замыкание обработчика событий запоминает clickCount.
3. Фабрики функций
Вы можете создавать функции с предустановленными аргументами:
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplyBy(2);
console.log(double(5)); // 10
Распространённые ошибки и как их избежать
Замыкания могут привести к неожиданному поведению, если их не понимать.
Захват переменной цикла
Использование var в циклах создаёт единственную общую привязку:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // prints 3, 3, 3
}, 100);
}
Все колбэки используют одну и ту же i. Чтобы исправить, используйте let (блочная область видимости) или IIFE:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // prints 0, 1, 2
}, 100);
}
Утечки памяти
Замыкания сохраняют переменные живыми. Если замыкание прикреплено к долгоживущему объекту (например, элементу DOM), оно может препятствовать сборке мусора. Всегда удаляйте обработчики событий, когда они больше не нужны.
Цепочка областей видимости и разрешение переменных
Когда вы обращаетесь к переменной, JavaScript ищет по цепочке областей видимости: сначала текущая область, затем внешние области, вплоть до глобальной области. Если не найдено, выбрасывается ReferenceError.
Пример:
let a = 'global';
function outer() {
let b = 'outer';
function inner() {
let c = 'inner';
console.log(a, b, c); // all accessible
}
inner();
}
outer();
Цепочка областей видимости для inner: inner -> outer -> global.
Сравнение: var, let и const
| Ключевое слово | Область видимости | Поднятие | Переназначаемое |
|---|---|---|---|
| var | Функция | Да (инициализируется undefined) | Да |
| let | Блок | Да (но не инициализируется, TDZ) | Да |
| const | Блок | Да (но не инициализируется, TDZ) | Нет |
Понимание этих различий является ключом к избежанию ошибок, связанных с областью видимости.
FAQ
Что такое замыкание в JavaScript?
Замыкание — это функция, которая сохраняет доступ к своей лексической области видимости, даже когда выполняется вне этой области. Оно позволяет функции «запоминать» переменные из места, где она была определена.
Почему замыкания важны?
Замыкания обеспечивают приватность данных, фабрики функций и обработчики событий, которые сохраняют состояние. Они фундаментальны для многих паттернов JavaScript.
Как избежать ошибок, связанных с замыканиями?
Используйте let и const для блочной области видимости, будьте внимательны с циклами и очищайте обработчики событий, чтобы предотвратить утечки памяти.
Готовы проверить своё понимание? Попробуйте наш JSON Formatter, чтобы экспериментировать с фрагментами кода и увидеть, как замыкания работают в реальном времени.