Lexical scope means that which variables a piece of code can see is decided by the nesting of the source text, not by the call stack. A function sees its own bindings, then those of the function or block around it, and so on outward to the global scope.
The mechanism, in spec terms:
- Every scope is an Environment Record with an
[[OuterEnv]]pointer to the record that surrounds it in the source. - When a function is created, it stores the current environment in its
[[Environment]]slot. - When it is called, a new Function Environment Record is created whose
[[OuterEnv]]is that stored environment. The caller's environment plays no part. - To resolve an identifier,
GetIdentifierReferencechecks the current record, then follows[[OuterEnv]]one link at a time. The first record with a binding wins; reachingnullwithout a hit means the name is unresolvable, which throws aReferenceErroron read.
That chain is the scope chain. A nearer binding hides a farther one of the same name; this is shadowing. The kinds of scope are the global scope, module scope, function scope, block scope (including catch and for loop heads), and class static blocks.
Contrast this with dynamic scope, where a function sees the caller's variables. JavaScript doesn't do that for identifiers. The one thing that is chosen by the call site is this.
Because the chain is known from the source, engines resolve most names at compile time. V8, for instance, resolves each reference to a stack slot or a context slot when it compiles the function, rather than searching by name at run time.