Область видимости и hoisting в JavaScript
Содержание
О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».
Область видимости — правило, по которому JavaScript решает, какие переменные доступны в данной точке кода. Понять его — значит перестать получать ReferenceError и undefined из ниоткуда.
Три области
const global = 'виден везде'; // глобальная область
function outer() {
const funcScoped = 'виден в outer'; // область функции
if (true) {
let blockScoped = 'виден в if'; // область блока
var functionWide = 'виден во всей outer'; // var игнорирует блок!
}
console.log(functionWide); // работает — var виден во всей функции
console.log(blockScoped); // ReferenceError — let живёт только в if
}
Три уровня:
- глобальная — вне всех функций, видна везде (и это обычно плохо — глобальные переменные связывают код);
- функции — внутри функции, недоступна снаружи;
- блока — внутри
{}(if, циклы, просто скобки), дляletиconst.
var против let: главная разница
function example() {
if (true) {
var a = 1; // виден во ВСЕЙ example
let b = 2; // виден только в этом блоке
}
console.log(a); // 1
console.log(b); // ReferenceError
}
var признаёт только область функции — блоки для него не существуют. let и const уважают блоки. Это ключевое отличие, из-за которого let вытеснил var: блочная область предсказуемее и убирает баги вроде утечки счётчика из цикла.
Hoisting: всплытие объявлений
Код видит переменные и функции «раньше», чем они написаны, — из-за всплытия:
console.log(a); // undefined — не ошибка
var a = 5;
foo(); // работает — объявление всплыло целиком
function foo() { console.log('привет'); }
console.log(b); // ReferenceError — мёртвая зона
let b = 5;
Hoisting поднимает объявления в начало области, но по-разному:
- 1 function declarationВсплывает целиком с телом — вызывается до объявления
-
2
varОбъявление всплывает, значение —
undefinedдо присваивания - 3 let / constВсплывают в мёртвую зону — обращение до строки = ошибка
-
4
function expressionКак переменная:
const fnв мёртвой зоне до объявления
Временная мёртвая зона
Отдельное понятие, которое стоит знать по имени. TDZ (temporal dead zone) — промежуток от начала блока до строки объявления let/const, где переменная уже «существует» (всплыла), но трогать её нельзя:
{
// здесь начинается мёртвая зона для x
console.log(x); // ReferenceError, а не undefined
let x = 5; // здесь она кончается
}
TDZ — не баг, а защита. Обращение к переменной до объявления почти всегда ошибка, и ReferenceError честнее молчаливого undefined, который дал бы var. Это одна из причин, по которой let безопаснее.
Цепочка областей
Когда функция ищет переменную, она смотрит по цепочке — от своей области наружу:
const level0 = 'глобальный';
function outer() {
const level1 = 'внешний';
function inner() {
const level2 = 'внутренний';
console.log(level2, level1, level0); // видит все три
}
inner();
}
inner видит свои переменные, переменные outer и глобальные — идёт наружу, пока не найдёт. Обратное невозможно: outer не видит level2 из inner. Это как вложенные комнаты: изнутри видно наружу, снаружи внутрь — нет.
Лексическая область: определяется местом объявления
Ключевой принцип, из которого растут замыкания. Область функции определяется тем, где функция написана, а не откуда её вызвали.
const name = 'глобальный';
function greet() {
console.log(name); // всегда 'глобальный' — видит место, где ОБЪЯВЛЕНА
}
function caller() {
const name = 'локальный';
greet(); // выведет 'глобальный', а не 'локальный'!
}
greet объявлена в глобальной области, поэтому видит глобальный name — неважно, что вызвали её из caller, где есть свой name. Это и называется лексической (статической) областью видимости: связи между функцией и переменными определяются при написании кода, а не при выполнении.
Именно поэтому замыкание помнит переменные места, где родилось: функция навсегда связана со своим лексическим окружением.
Смежные темы: переменные — var, let, const; замыкания — лексическая область на практике; this и контекст — а это уже про вызов. Полный список — в уроках JavaScript.