Variable environment & invocation
You already know the engine uses a global execution context,
moves through creation then execution, and that hoisting
makes var and function bindings visible before the lines that assign them. This page ties
that together with function invocation: what happens when the same identifier (here,
x) appears in global code and inside multiple functions.
One identifier, many bindings
Each execution context has its own variable environment (memory). A new invocation
allocates a new local environment. Reusing the name x in a, in b, and at the top
level does not create a single shared box — you get separate bindings that happen to
shadow the outer name while that function runs.
One name, three independent bindings
The program
This is the classic “Namaste style” example: global x, two functions that each declare
their own var x, then calls and a final global log. With var and function
declarations, the story matches the two-phase model from earlier lessons (lexical scoping
with let / const is stricter, but the per-invocation memory idea is the same).
var x = 1
function a() {
var x = 10
console.log(x)
}
function b() {
var x = 100
console.log(x)
}
a()
b()
console.log(x)
Console output: 10, then 100, then 1. The middle line surprises people who expect
“the” x to keep updating — but each console.log looks up x in the active
context’s environment first.
What happens, step by step
- After the global creation phase,
xisundefinedanda/brefer to callables (see Hoisting if you need that picture). x = 1only touches global memory.- Calling
a()creates a new context: a localx(again starting asundefinedin the local creation phase) is not the same slot as globalx. Assigning10and logging prints 10. - When
areturns, that entire local environment is discarded — the globalxwas 1 the whole time. b()repeats the pattern with a fresh localx, hence 100 in the log.- The last
console.log(x)runs in global code, so it reads globalx→ 1.
Global vs local — same name, different environments
End of the global creation phase: var x exists as undefined; function a and function b already reference their full callables — same two-phase model as the Hoisting lesson.
Call stack, briefly
While a or b runs, the call stack holds the global frame
underneath a function frame. Finishing a function pops the top frame; the engine
resumes the call site in the outer context. Nothing in the inner log rewrites the
outer binding unless you assign to a name the outer scope actually owns (or use
closures — a later topic).
Recap
- Every invocation gets its own execution context and its own variable environment.
- The same spelling of an identifier in an inner
varbinding is a different binding from the global one — it shadows the outer name for lookups inside that function. - Lookup starts in the current environment; that is why logs show 10, 100, then 1, not a single running total.
✦Next: Event loop — how async work and the call stack interact at a higher level.