Step 1 of 5 · Reading · ~4 min
Learn
Scope and Closures
Block Scope and Hoisting
JavaScript Fundamentals used let and const inside blocks without asking where a name stops being visible. This lesson answers that exactly: which names a line of code can see, why var breaks the rules, and what "hoisting" really is. By the end you can predict what a snippet prints without running it.
Scopes nest, and lookup walks outward
A scope is the region of code where a name can be used. A function body is a scope, and so is every { ... } block (an if, a for, or a bare pair of braces). Scopes sit inside each other. When the engine meets a name it looks in the current scope, then the one around it, then the next, up to the top level. This is the scope chain.
let and const stop at the block, var does not
let and const belong to the nearest { }. var belongs to the nearest function (or the whole file) and ignores blocks completely:
With let i the return i line would throw a ReferenceError, because that i ceased to exist at the closing brace of the loop. That leak is why modern code avoids var.
Shadowing
A declaration with the same name as an outer one shadows it: inside that scope the inner name wins, and the outer variable is untouched.
Declaring the same let name twice in one scope is a SyntaxError, and because it is found before any code runs, the whole file refuses to start.
Hoisting: declarations exist before their line runs
When a scope is entered, the engine first creates every binding declared in it, and only then runs the code. That is hoisting. What differs is the state each kind of binding starts in:
| Declaration | Before its own line |
|---|---|
function f() {} | fully usable, the body is already attached |
var x = 1 | exists, holds undefined |
let, const, class (classes come later in this course) | exists but uninitialised: touching it throws ReferenceError |
The span between entering the scope and reaching the let line is the temporal dead zone (TDZ). That let is hoisted too, and this is how you can prove it:
If the inner mode did not exist yet, the lookup would reach the outer "dark". It throws because the inner one already exists, shadows the outer, and is still uninitialised. Even typeof is not safe in the TDZ: typeof neverDeclared gives "undefined", but typeof on a let before its line throws.
A function stored in a variable follows the variable's rule, not the declaration's. var greet = function () {} called one line early fails with TypeError: greet is not a function, because greet is undefined at that moment.
Common mistakes
- Reading a loop's
varcounter after the loop and trusting it. It leaks, and so does anyvarset inside anif. - Expecting
console.log(x)beforevar x = 5to throw. It printsundefined, which hides the ordering bug. - Expecting the same before
let x = 5. That one throws. - Declaring a
lettwice in one scope, for example after pasting code. The file fails before printing anything.
Your exercise
You will build the thing this lesson describes: a scope chain. The starter runs a tiny script of scope commands and asks your three scope functions the questions. The usual slip is var, which must skip past block scopes to the nearest function scope.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…