Scope Chain, Scope and Lexical Environment
This article explores three fundamental concepts in JavaScript: Scope, Lexical Environment, and the Scope Chain. Understanding these is crucial for writing predictable and bug-free code.
- What is Scope?
- What is Lexical Environment?
- What is Scope Chain?
Scope in JavaScript is intrinsically linked to the Lexical Environment. Let's start with an example:
When the JavaScript engine executes console.log(b)
inside function a()
, it first looks for b
within the local scope (memory space) of a()
. Since b
wasn't declared inside a()
, it's not found there.
What happens next? Will it print undefined
, throw an error, or find the value?
Running this code gives:
Somehow, the function a()
was able to access the variable b
defined outside of it.
Let's add another layer of nesting:
Running this version yields the same result:
Function c()
can also access the global variable b
.
Now, let's try the opposite: define b
inside a()
and try to access it globally:
This time, we get an error:
This error occurs because b
is defined only within the scope of a()
and is not accessible outside of it. This brings us to the concept of Scope.
What is Scope? 🎯
Scope determines the accessibility or visibility of variables and functions in your code during runtime. It answers two main questions:
- What is the scope of a specific variable/function? (Where is it accessible?)
- Is a variable/function within the current scope? (Can I access it from here?)
Consider the previous example where the ReferenceError
occurred:
When this program runs:
- A Global Execution Context (GEC) is created and pushed onto the Call Stack.
a()
is invoked. A new Execution Context fora
is created and pushed onto the stack.- Memory is allocated within
a
's context for its local variableb
(initiallyundefined
) and local functionc
. - The code inside
a
executes:b
is assigned the value10
. c()
is invoked. A new Execution Context forc
is created and pushed onto the stack.c
executes (it's empty in this version). Its context is popped from the stack.a
finishes. Its context is popped from the stack.- The global
console.log(b)
executes. The engine looks forb
in the GEC. It doesn't find it, resulting in theReferenceError
.
What is Lexical Environment?
Whenever an Execution Context is created, a Lexical Environment is also created.
Info
Lexical Environment = Local Memory + Reference to the Lexical Environment of its Parent.
Lexical refers to the way code is physically written or structured. It means hierarchy or sequence based on the code structure, not the call order.
In this code:
- Function
c
is lexically nested within functiona
. - Function
a
is lexically nested within the global scope.
So:
- The Lexical Environment for
c
contains its local memory (empty in this case) and a reference to the Lexical Environment ofa
. - The Lexical Environment for
a
contains its local memory (b
,c
) and a reference to the Lexical Environment of the Global Scope. - The Lexical Environment for the Global Scope contains its memory (
a
) and a reference tonull
(it has no outer lexical environment).
Let's visualize the parent references (represented by the orange box in the original description):
How the Scope Chain Works ⛓️
Let's revisit the example where b
was accessed from within c
:
When console.log(b)
runs inside c
:
- The JS engine looks for
b
in the local memory (Lexical Environment) ofc
. It's not found. - The engine follows the reference to the parent's Lexical Environment, which is
a
's environment. - It looks for
b
ina
's local memory. It findsb
with the value10
. - The value
10
is retrieved and printed to the console.
What if b
was defined globally instead?
Now, the lookup process is:
- Look for
b
inc
's local memory. Not found. - Follow the reference to
a
's Lexical Environment. Look forb
. Not found. - Follow the reference from
a
's environment to the Global Lexical Environment. Look forb
. Found! (b = 10
). - The value
10
is retrieved and printed.
Finally, what if b
is not defined anywhere?
Lookup process:
- Look for
b
inc
's local memory. Not found. - Follow reference to
a
's Lexical Environment. Look forb
. Not found. - Follow reference to Global Lexical Environment. Look for
b
. Not found. - The engine has reached the end of the chain (
null
reference from Global LE) without findingb
. - A
ReferenceError
is thrown.
In this case, we say "b
is not in the scope."
Scope Chain
This mechanism of searching for a variable by looking in the current local scope, and if not found, looking in the parent's lexical environment, and then the grandparent's, and so on, up to the global scope, is called the SCOPE CHAIN.
Summary ✨
- A Lexical Environment is created with every Execution Context. It consists of the Local Memory (variables/functions defined within that context) plus a reference to the parent's Lexical Environment.
- The parent (Lexical Parent) is determined by where a function is physically written in the code, not by how it's called.
- The Scope Chain is the chain of Lexical Environments, linked by their parent references. It dictates whether a variable or function is accessible (in scope) from a particular point in the code.
Scope Chain in the Browser Debugger
You can observe the Scope Chain using browser developer tools. Set a breakpoint inside the innermost function and inspect the "Scope" panel.
When paused at the breakpoint:
-
Call Stack: Shows the active execution contexts (
c
,a
,(anonymous)
for global). -
Scope Panel (for
a
if inspected earlier): Would show its Local scope (b
,c
) and its closure scope pointing to Global. -
Scope Panel (for
c
): Shows its Local scope (empty), its Closure scope (containingb
froma
), and the Global scope. This visually represents the Scope Chain.