跪拜 Guibai
← All articles
JavaScript

JavaScript Scope Isn't Magic — It's a Lookup Rule You Can Debug in Five Questions

By 柚yuzumi ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Scope bugs are among the most common runtime errors in JavaScript and are notoriously hard to spot when `var`, `let`, and `const` mix in a codebase. Understanding the temporal dead zone and block-scoping rules directly prevents the `ReferenceError` that stops a page from loading or breaks a module in production.

Summary

JavaScript engines parse code into an AST before execution, establishing where every variable lives. That structure determines whether a `console.log` succeeds or throws a `ReferenceError`. Three scope types govern access: global, function, and block. Function scope walls off variables declared inside a function, regardless of how many times that function is called. Block scope, introduced by `let` and `const`, confines variables to curly braces — a boundary `var` ignores entirely.

Hoisting lifts `var` declarations but not their assignments, producing `undefined` instead of the expected value. `let` and `const` hoist differently: the engine reserves the name for the entire block but forbids access before the declaration line, a window called the temporal dead zone. That dead zone also means an inner `let` shadows an outer variable immediately upon entering the block, not at the declaration line, so a read before `let a = 10` fails even when an outer `a` exists.

A five-question checklist cuts through the confusion: where is the variable declared, where is the code using it, which keyword declared it, is there a same-name variable in the current scope, and has initialization finished. Answering those questions resolves nearly every `is not defined` and `cannot access before initialization` error.

Takeaways
JavaScript engines parse source into an AST before execution, which is when variable locations and scope relationships are fixed.
Three scope types exist: global scope, function scope, and block scope (the last only for `let` and `const`).
`var` declarations are hoisted and initialized to `undefined`; assignments stay in place, so reading before assignment yields `undefined`.
`let` and `const` are hoisted but not initialized, creating a temporal dead zone from block entry until the declaration line.
Inside a block, a `let` or `const` declaration shadows an outer variable for the entire block, not just after the line where it appears.
`var` ignores block boundaries — a `var` inside `{}` is accessible outside the braces.
`const` prevents reassignment of the binding but does not make objects immutable; properties can still be changed.
Five diagnostic questions resolve most scope errors: declaration location, usage location, declaration keyword, same-name shadowing, and initialization status.
Conclusions

Many developers assume a function's local variables become accessible after the function runs; the engine's static scope resolution makes runtime call count irrelevant.

The temporal dead zone is often taught as 'don't use before declaring,' but the more dangerous behavior is that it shadows outer variables for the entire block, causing failures even when an outer fallback seems available.

`var`'s lack of block scope is not just a legacy quirk — it actively breaks the mental model developers build when they see curly braces, making mixed `var`/`let` codebases a persistent source of bugs.

Concepts & terms
Lexical Scoping
A scoping model where a variable's accessible range is determined by its position in the source code at parse time, not by the runtime call stack. JavaScript uses lexical scoping, which is why a function's local variables are never visible outside it regardless of how or when the function is called.
Temporal Dead Zone (TDZ)
The period between entering a block and the execution of a `let` or `const` declaration within it. During the TDZ, the variable name is reserved for the block but any read or write throws a ReferenceError. The TDZ also causes the inner variable to shadow an outer variable with the same name for the entire block, not just after the declaration line.
Hoisting
The JavaScript engine's behavior of processing variable and function declarations during the parse phase before executing code. `var` declarations are hoisted and initialized to `undefined`; `let` and `const` are hoisted but remain uninitialized, which is why they trigger the temporal dead zone.
Block Scope
A scope boundary created by a pair of curly braces `{}` (in `if`, `for`, `while`, or standalone blocks). Variables declared with `let` or `const` inside a block are confined to it; `var` ignores block scope entirely and leaks to the enclosing function or global scope.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗