Область видимости и 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. 1 function declarationВсплывает целиком с телом — вызывается до объявления
  2. 2 varОбъявление всплывает, значение — undefined до присваивания
  3. 3 let / constВсплывают в мёртвую зону — обращение до строки = ошибка
  4. 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.

Частые вопросы

Что такое область видимости?
Часть кода, где переменная доступна. Глобальная область видна везде, область функции — внутри функции, область блока — внутри фигурных скобок. Переменная существует только в своей области и вложенных в неё.
Что такое hoisting простыми словами?
Механизм, при котором объявления переменных и функций как будто поднимаются в начало области. var поднимается со значением undefined, let и const — в мёртвую зону, function declaration — целиком с телом.
Что такое временная мёртвая зона (TDZ)?
Промежуток от начала блока до строки объявления let или const, где переменная уже существует, но обращение к ней даёт ReferenceError. Она защищает от использования переменной до объявления.
Чем область блока отличается от области функции?
var виден во всей функции, игнорируя блоки. let и const видны только в своём блоке — внутри if, цикла, любых фигурных скобок. Блочная область убирает целый класс ошибок с var.
Что такое лексическая область видимости?
Область определяется тем, ГДЕ функция написана в коде, а не откуда вызвана. Функция видит переменные того места, где объявлена, — на этом построены замыкания.