Learning Book

Лексическое окружение и цепочка scope

Что такое лексическое окружение

Каждый раз, когда JavaScript выполняет код — функцию, блок {} или глобальный скрипт — создаётся лексическое окружение (Lexical Environment). Это внутренняя структура из двух частей:

  1. Environment Record — таблица, где хранятся все переменные и функции этого скоупа
  2. Ссылка на внешнее окружение ([[OuterEnv]]) — указатель на родительский скоуп
// Глобальное окружение
let x = 10

function outer() {
  // Окружение outer: { y: 20, inner: function }
  // [[OuterEnv]] → глобальное окружение
  let y = 20

  function inner() {
    // Окружение inner: { z: 30 }
    // [[OuterEnv]] → окружение outer
    let z = 30
    console.log(x + y + z) // 60 — нашли x и y по цепочке
  }

  inner()
}

Как работает поиск переменных

Когда JavaScript встречает имя переменной, он ищет её вверх по цепочке окружений:

  1. Смотрит в текущий Environment Record
  2. Если не нашёл — переходит по [[OuterEnv]] к родительскому окружению
  3. Повторяет до глобального окружения
  4. Если в глобальном тоже нет — ReferenceError
let name = 'Глобальный'

function greet() {
  let greeting = 'Привет'
  // greeting — в текущем окружении ✓
  // name — не в текущем, ищем выше → нашли в глобальном ✓
  console.log(greeting + ', ' + name)
}

greet() // "Привет, Глобальный"
Цепочка скоупов определяется **лексически** — во время написания кода, а не во время выполнения. Именно поэтому она называется «лексическое» окружение.

Лексический vs динамический скоуп

JavaScript использует лексический скоуп: внешнее окружение функции — это то место, где функция написана, а не откуда вызвана.

let value = 'глобальный'

function showValue() {
  console.log(value) // берёт value из своего [[OuterEnv]]
}

function wrapper() {
  let value = 'локальный'
  showValue() // всё равно выведет 'глобальный'
}

wrapper()
// Вывод: "глобальный"
// showValue написана в глобальном контексте → её [[OuterEnv]] = глобальное окружение
В спецификации ECMAScript различают два типа Environment Record:
  • Declarative Environment Record — для let, const, var, функций, параметров
  • Object Environment Record — для with (устаревший паттерн) и глобального объекта

Глобальное окружение особое: его Environment Record — это объект globalThis. Поэтому globalThis.x и просто x в глобальном скоупе — одно и то же (для var и функций, но не для let/const).

Environment Record для разных конструкций

КонструкцияСоздаёт новый скоуп?Тип
function() {}ДаFunction Environment
() => {}Да (без своего this)Function Environment
{ } блокДа (для let/const)Block Environment
if, for, whileДа (для let/const)Block Environment
var в блокеНет — поднимается в функцию
function example() {
  var a = 1  // живёт в окружении example

  if (true) {
    let b = 2  // живёт в окружении блока if
    var c = 3  // живёт в окружении example (пробивает блок!)
    console.log(a) // 1 — нашли в родительском окружении
  }

  console.log(c) // 3 — var пробил блок, доступен в функции
  // console.log(b) // ReferenceError — b живёт только в if-блоке
}

Практический вывод

Понимание лексического окружения объясняет:

  • Почему замыкания «запоминают» переменные — они хранят ссылку на [[OuterEnv]]
  • Почему var в блоке «утекает» наружу
  • Почему одноимённые переменные в разных скоупах не конфликтуют