Lexical Environment
Каждый раз, когда в программе вызывается функция, внутри интерпретатора создается специальный словарь LexicalEnvironment (лексическое окружение), привязанный к этому вызову. (Lexical Env - abstract data structure, dictionary)
Execution context pushed to call stack
The Execution Context goes on the stack, which contains a reference to the LE, not the LE itself.
Scope Chain Resolution
When accessing a variable:
Check current Environment Record
If not found, follow Outer reference
Repeat until found or reach
null(ReferenceError)
Execution Context contains:Lexical Environment (let, const, function declarations)
Variable Environment (var declarations - usually same as LE)
thisbinding
Key difference: Execution Context is runtime, Lexical Environment is the scope mechanism.
Имя определения (идентификатор, то есть имя константы, переменной и так далее) становится ключом, а значение определения становится значением в словаре. К таким определениям относятся аргументы, константы, функции, переменные и т.д.
Окружение — это не «все, что было объявлено до функции, в которой мы используем эти объявления». Не важно, чтоnumberпоявился позже использования внутри функции. Главное, что вызов функцииsquareпроисходит позже определенияnumber, а значит к этому времени идентификатор уже был добавлен в окружение, внутри которого была создана функцияsquare.
Когда мы работаем с константами, все просто. Нет изменений — нет проблем. В случае с переменными ситуация становится сложнее.
Изменение переменной следует читать как «изменение значения ключа в окружении». Соответственно, обращение кnumberвсегда вернет последнее присвоенное значение. Завязка на переменные, описанная в коде выше, должна восприниматься как абсолютное зло. Она порождает неявные зависимости, сложный код и отладку. Функция автоматически перестает быть чистой, так как начинает зависеть от внешнего контекста.
интерпретатор производит поиск значения идентификатора не только в локальном лексическом окружении (в том, где используется идентификатор), но и во внешнем окружении. Поиск начинается с локального окружения, и если в нем не найден нужный идентификатор, то просмотр идет дальше, вплоть до уровня модуля, а затем и до глобального уровня.
Внешним окружением по отношению к функции считается окружение, в котором функция была объявлена (а не вызвана!). Если разбить пример выше на два файла, то разница станет очевидной.
Окружение есть не только у функций. Любой идентификатор, определенный на уровне модуля, попадает в лексическое окружение модуля. Кроме того, существует и глобальное окружение. Благодаря ему мы с легкостью используем в JS такие функции, как console.log или Math.sqrt, даже особо не задумываясь, откуда они берутся.
Область видимости (scope) — это широкое понятие, означающее, грубо говоря, «интерпретатор в разных местах кода видит разные штуки».
Лексическая область видимости (Lexical scoping) — это конкретный механизм, одно из правил для области видимости. Этот механизм применяется в JavaScript и большинстве других языков. Под лексической областью видимости можно понимать просто механизм поиска значений: смотрим в текущей области, если нет — идём на уровень выше, и так далее. Слово «лексический» означает, что видимость задаётся исключительно текстом программы, исходным кодом. То есть можно смотреть на программу, не запуская её, и понять область видимости в любой точке.
Окружение (environment) - это область памяти, где записываются идентификаторы и значения из областей видимости. Не путайте с окружением, как средой исполнения.
Этот механизм называется лексической областью видимости. Область видимости любого компонента определяется местом расположения этого компонента внутри кода. И вложенные блоки имеют доступ к их внешним областям видимости.
fn(a) -> it's like fn(let a) - just variable
Область видимости (scope) компонентов — это местоположение, где эти компоненты доступны.
Компоненты, созданные снаружи функций, инструкций с if, циклов и так далее, находятся в глобальной области видимости
Фигурные скобки
{ }задают новую локальную область видимости
Это замыкание: сочетание функции и окружения, где она была заявлена.
Temporal Dead Zone (TDZ) - explain on browser dev tools show example
The period between entering a scope and the actual declaration where a variable exists but cannot be accessed. Accessing it throws a ReferenceError.
let, const, class has TDZ (var without TDZ, undefined)
Arrow functions follow the hoisting rules of their declaration keyword (var/let/const).
Function Declarations - Fully Hoisted
Variables exist in the Environment Record from block start but are uninitialized:
The binding exists in the LE but accessing before initialization throws an error.
// Declaration: added to LE during creation phase (hoisted)
function declared() {}
// Expression: added during execution phase
const expressed = function() {};
Golden Rule: Function declarations hoist completely, function expressions follow their declaration keyword's rules.