Лексическое окружение и цепочка scope
Что такое лексическое окружение
Каждый раз, когда JavaScript выполняет код — функцию, блок {} или глобальный скрипт — создаётся лексическое окружение (Lexical Environment). Это внутренняя структура из двух частей:
- Environment Record — таблица, где хранятся все переменные и функции этого скоупа
- Ссылка на внешнее окружение (
[[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 встречает имя переменной, он ищет её вверх по цепочке окружений:
- Смотрит в текущий Environment Record
- Если не нашёл — переходит по
[[OuterEnv]]к родительскому окружению - Повторяет до глобального окружения
- Если в глобальном тоже нет —
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в блоке «утекает» наружу - Почему одноимённые переменные в разных скоупах не конфликтуют