Область видимости и hoisting
Почему переменная, объявленная внутри функции, недоступна снаружи? Почему можно вызвать function declaration до её объявления? Всё это объясняет область видимости и механизм hoisting — «поднятия».
🔭 Область видимости: где «видна» переменная
Область видимости (scope) — это часть кода, где переменная или функция доступна. Представьте здание с комнатами: вещи в комнате доступны только тем, кто находится в этой комнате. Вещи в коридоре доступны всем.
В JavaScript есть несколько уровней видимости. Начнём с самых базовых.
🌍 Глобальная область видимости
Переменные, объявленные вне любых функций и блоков, находятся в глобальной области видимости. Они доступны из любого места кода — из любой функции, из любого блока.
let globalVar = "Я глобальная!"; // глобальная переменная
function showGlobal() {
console.log(globalVar); // "Я глобальная!" — доступна внутри функции
}
function modifyGlobal() {
globalVar = "Изменена!"; // можно изменять изнутри
}
showGlobal(); // "Я глобальная!"
modifyGlobal();
showGlobal(); // "Изменена!"
console.log(globalVar); // "Изменена!"
⚠️ Предупреждение: глобальные переменные — это удобно, но опасно. Любая функция может их изменить. В больших программах это приводит к неожиданным ошибкам — трудно отследить, кто изменил переменную. Старайтесь минимизировать использование глобальных переменных.
📦 Локальная (функциональная) область видимости
Переменные, объявленные внутри функции, доступны только в этой функции и вложенных в неё функциях. Снаружи они недоступны — как вещи в закрытой комнате.
function calculateBMI(weight, height) {
let bmi = weight / (height * height); // локальная переменная
let category;
if (bmi < 18.5) {
category = "Недостаточный вес";
} else if (bmi < 25) {
category = "Норма";
} else {
category = "Избыточный вес";
}
return "ИМТ: " + bmi.toFixed(1) + " (" + category + ")";
}
console.log(calculateBMI(70, 1.75)); // "ИМТ: 22.9 (Норма)"
// Попытка получить локальную переменную снаружи:
console.log(bmi); // ❌ ReferenceError: bmi is not defined
console.log(category); // ❌ ReferenceError: category is not defined
Это хорошая вещь! Локальные переменные — приватные для функции. Разные функции могут использовать одинаковые имена переменных без конфликтов:
function calculateSquare(n) {
let result = n * n; // своя переменная result
return result;
}
function calculateCube(n) {
let result = n * n * n; // своя переменная result — не конфликтует!
return result;
}
console.log(calculateSquare(4)); // 16
console.log(calculateCube(3)); // 27
// Оба result — разные переменные в разных областях видимости
🧱 Блочная область видимости: let и const
let и const имеют блочную область видимости — они «живут» только внутри блока { }, в котором объявлены. Блок — это любые фигурные скобки: if, for, while, просто {}.
if (true) {
let blockVar = "Я внутри блока";
const blockConst = "Я тоже внутри";
console.log(blockVar); // "Я внутри блока" — OK
console.log(blockConst); // "Я тоже внутри" — OK
}
console.log(blockVar); // ❌ ReferenceError: blockVar is not defined
console.log(blockConst); // ❌ ReferenceError: blockConst is not defined
// В цикле for — i доступна только внутри цикла
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // ❌ ReferenceError: i is not defined
⚠️ var vs let: почему var опасен
До ES6 все переменные объявлялись через var. Проблема var: у него нет блочной области видимости! var «вытекает» из блоков — это очень неинтуитивно и приводит к ошибкам.
// var НЕ имеет блочной области видимости:
if (true) {
var leaked = "Я утекла!";
}
console.log(leaked); // "Я утекла!" — var вытек из блока if!
// В цикле:
for (var i = 0; i < 3; i++) {
// что-то делаем
}
console.log(i); // 3 — i вытекла из цикла!
🚫 Вывод: не используй var в современном коде. Всегда используй let (для изменяемых переменных) и const (для неизменяемых). var остался в языке для обратной совместимости, но новый код на нём не пишут.
У var есть функциональная область видимости — он «видит» только функцию, но не блок. Единственный «сдерживающий» его барьер — фигурные скобки функции:
function example() {
if (true) {
var x = 10; // var — функциональная область видимости
let y = 20; // let — блочная область видимости
}
console.log(x); // 10 — var доступна в функции
console.log(y); // ❌ ReferenceError — let не доступна
}
🚀 Hoisting: «поднятие» объявлений
Hoisting (поднятие) — это механизм JavaScript, при котором объявления функций и переменных «поднимаются» наверх своей области видимости ещё до выполнения кода. Не физически — просто интерпретатор обрабатывает их в первую очередь.
// Можно вызвать function declaration ДО объявления!
sayHello(); // "Привет!" — работает!
function sayHello() {
console.log("Привет!");
}
// JavaScript "поднял" объявление функции наверх файла
Что реально происходит — JavaScript как будто переписывает код так:
// Что видит разработчик:
sayHello();
function sayHello() { console.log("Привет!"); }
// Что "видит" JavaScript после hoisting:
function sayHello() { console.log("Привет!"); } // поднято наверх
sayHello(); // теперь функция уже объявлена — вызов работает
💡 Hoisting объявлений функций — одна из причин, почему function declaration очень удобны. Можно организовать код так, чтобы главная логика была наверху файла, а вспомогательные функции — внизу. Читать удобнее.
🚧 Temporal Dead Zone: let, const и function expression
let, const и function expression тоже «поднимаются», но с одним важным отличием: они не инициализируются. Доступ к ним до строки объявления вызывает ошибку — они находятся в Temporal Dead Zone (TDZ).
// Function Expression — нельзя вызвать до строки с const
greet("Иван"); // ❌ ReferenceError: Cannot access 'greet' before initialization
const greet = function(name) {
console.log("Привет, " + name);
};
// Стрелочная функция — то же самое
double(5); // ❌ ReferenceError
const double = n => n * 2;
// var с function expression — особый случай:
sayHi(); // ❌ TypeError: sayHi is not a function
var sayHi = function() { console.log("Hi!"); };
// var поднимается, но БЕЗ значения — sayHi = undefined до строки присваивания
Сводная таблица поведения при hoisting
| Тип | Поднимается? | Доступно до строки? |
|---|---|---|
function declaration |
✅ Да, полностью | ✅ Да |
var |
✅ Да, но без значения | ⚠️ Да, но = undefined |
let |
✅ Да (TDZ) | ❌ Нет (ReferenceError) |
const |
✅ Да (TDZ) | ❌ Нет (ReferenceError) |
🔗 Цепочка областей видимости (Scope Chain)
Когда JavaScript встречает переменную, он ищет её по цепочке: сначала в текущей области видимости, затем в внешней, затем ещё выше — вплоть до глобальной.
let globalName = "глобальная";
function outer() {
let outerName = "внешняя";
function inner() {
let innerName = "внутренняя";
// inner видит все три переменные:
console.log(innerName); // "внутренняя" — своя
console.log(outerName); // "внешняя" — из outer
console.log(globalName); // "глобальная" — из глобальной области
}
// outer видит только свою и глобальную:
console.log(outerName); // "внешняя" — своя
console.log(globalName); // "глобальная" — из глобальной области
// console.log(innerName); // ❌ ReferenceError — не видит inner-переменную
inner();
}
outer();
Поиск идёт только наверх — внутренняя функция видит внешние переменные, но не наоборот. Это основа механизма замыканий, о которых поговорим в следующем уроке.
✅ Итоги урока
- Глобальная область видимости — переменные вне функций и блоков, доступны везде
- Локальная (функциональная) — переменные внутри функции, не видны снаружи
- Блочная —
letиconstвидны только в блоке{}, где объявлены varне имеет блочной области видимости — «вытекает» из блоков. Не используйvar- Hoisting: объявления function declaration поднимаются наверх — их можно вызвать до объявления
letиconstв TDZ — обращение до строки объявления вызывает ReferenceError- Цепочка областей видимости: поиск переменной идёт наверх — от локальной к глобальной
В следующем уроке разберём замыкания — один из самых интересных и мощных паттернов JavaScript, который напрямую вырастает из области видимости. 🚀
Тест: Область видимости и hoisting
7 вопросов