Замыкания и контекст — темы, на которых спотыкаются даже опытные разработчики. Без их понимания вы будете писать баги в асинхронном коде, обработчиках событий и React-компонентах. Разбираем до мельчайших деталей.
Лексическое окружение (Lexical Environment)
Всё начинается с лексического окружения. Это внутренний механизм JS, который хранит переменные и функции. Каждый блок кода, функция или скрипт имеют своё окружение.
let globalVar = 'Я виден везде';
function makeFunction() {
// Локальное окружение функции
let localVar = 'Я только внутри';
console.log(globalVar); // ✅ Доступно
}
console.log(localVar); // ❌ ReferenceError
Когда код обращается к переменной, JS идёт по цепочке окружений: от самого вложенного до глобального. Это называется Scope Chain.
Что такое замыкание (Closure)?
Замыкание — это функция, которая «запоминает» своё лексическое окружение даже после того, как внешняя функция завершила выполнение.
let count = 0;
return function() {
count++;
return count;
}
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
Переменная count должна была умереть после завершения createCounter(), но внутренняя функция «замкнула» её на себя. Это и есть замыкание.
Классическая ошибка с замыканием в цикле
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3
}, 100);
}
// ✅ Исправление через IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 100);
})(i);
}
// ✅ Современное исправление (let)
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
Почему так происходит? var не имеет блочной области видимости. Все три колбека ссылаются на одну переменную i, которая к моменту выполнения стала равна 3. let создаёт новую переменную для каждой итерации.
Контекст this: 4 правила определения
Значение this зависит от того, как вызвана функция. Запомните 4 простых правила.
1. Глобальный контекст
2. Вызов метода объекта
name: 'Алексей',
greet() {
console.log(this.name); // 'Алексей' — this ссылается на user
}
};
user.greet();
3. Вызов функции (не метод)
console.log(this); // window (в strict mode — undefined)
}
showThis();
4. Вызов с new (конструктор)
this.name = name; // this ссылается на новый объект
}
const alex = new User('Алексей');
Явная привязка this: call, apply, bind
Иногда нужно принудительно задать this. Для этого есть три метода.
console.log(greeting + ', ' + this.name + punctuation);
}
const user = { name: 'Елена' };
// call — аргументы через запятую
greet.call(user, 'Привет', '!'); // Привет, Елена!
// apply — аргументы массивом
greet.apply(user, ['Здравствуйте', '...']);
// bind — создаёт новую функцию с привязанным this
const boundGreet = greet.bind(user, 'Йо');
boundGreet('!'); // Йо, Елена!
Стрелочные функции и their this
Стрелочные функции не имеют своего this. Они берут его из внешнего лексического окружения.
name: 'Дмитрий',
regularFunc: function() {
console.log(this.name); // 'Дмитрий'
},
arrowFunc: () => {
console.log(this.name); // undefined (this = window)
}
};
Когда использовать стрелочные функции? В колбэках, чтобы не терять контекст. В методах объекта — осторожно, если нужен доступ к this объекта.
Практические примеры применения замыканий
1. Приватные переменные
let balance = initialBalance;
return {
getBalance: () => balance,
deposit: (amount) => { balance += amount; },
withdraw: (amount) => {
if (amount <= balance) balance -= amount;
}
};
}
const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
account.balance = 999999; // ❌ Не сработает, переменная приватная
2. Мемоизация (кэширование результатов)
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key] === undefined) {
cache[key] = fn(...args);
}
return cache[key];
};
}
const slowSquare = (n) => {
console.log('Вычисляю...');
return n * n;
};
const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // 'Вычисляю...' 25
console.log(fastSquare(5)); // 25 (из кэша, без вычисления)
3. Фабрика функций
return function(number) {
return number * factor;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
• Замыкание = функция + доступ к внешним переменным
• this определяется в момент вызова, а не создания
• Стрелочные функции не имеют своего this
• Для фиксации this используйте bind или стрелочные функции
• Замыкания потребляют память (переменные не удаляются сборщиком мусора, пока жива функция)
Заключение: Замыкания и контекст — фундаментальные концепции JS. Без них вы не поймёте, как работают React Hooks, асинхронный код, обработчики событий и многие паттерны. Практикуйтесь: напишите свой debounce, throttle, memoize. Изучите, как работают bind, call, apply. Через месяц эти концепции станут интуитивно понятными.