Scope Chain
The scope chain is the mechanism JavaScript uses to resolve variable lookups. It begins in the current scope and, if a variable is not found there, moves up through parent scopes until it reaches the global scope.
const globalVar = "global";
function outer() {
const outerVar = "outer";
function inner() {
const innerVar = "inner";
console.log(globalVar, outerVar, innerVar);
// inner can access all three variables via the scope chain
}
inner();
}
outer(); // Logs: global outer inner
Scope Chain Lookup Process
- The inner scope is checked first.
- If the variable isn’t found, JavaScript checks the outer scope.
- Still not found? It checks the global scope.
- If nowhere in the chain contains the variable, a
ReferenceErroroccurs.
Why the Scope Chain Matters
- Ensures functions retrieve the correct variables based on where they were written.
- Enables closures: functions can remember and use variables from outer scopes.
- Prevents accidental access to unrelated scopes.
Lexical Enviroment
A Lexical Environment is a internal data structure used by the JavaScript engine to manage variable and function resolution. It consists of two parts:
- Environment Record: Stores variables, constants, and function declarations in the current scope.
- Outer Environment Reference: Points to the parent lexical environment to support nested scope lookup.
Here's how the lexical environment works during execution:
function outer() {
const x = 1; // outer's Environment Record has x = 1
function inner() {
const y = 2; // inner's Environment Record has y = 2
console.log(x + y); // JS looks in inner's record (y),
// then outer’s via Outer Reference (x),
// and logs 3
}
inner();
}
outer();
In this example:
- The
innerfunction creates its own lexical environment containingy. - If a variable (like
x) isn’t in theinnerenvironment record, JavaScript follows the outer environment reference to find it inouter’s environment record. - This chain of lookups—current scope → outer scope → … → global scope—is what enables nested scoping.
As a result, x is resolved to 1 and y to 2, so the console logs 3.
Lexical Scope
Lexical scope describes where something is defined, not where it's used. It refers to the specific place in the source code where a variable, function, or expression was created. So we can say an item's (variable, function, or expression.) lexical scope is the place where it was defined, not where it's later called.
OrIn JavaScript, lexical scope (also known as “static scope”) means that a function can only access variables defined **where it was written**, not where it’s called. JavaScript resolves variables using the **chain of scopes built at compile time** — this rule goes one way (upwards), is reliable, and never changes.
Let's understand this with some example:
Example 1
// Global variable
const myName = "Oluwatobi";
function getName() {
return myName;
}
getName(); // returns "Oluwatobi"
Question: Is the lexical scope of myName the global space or the local (function) space of getName()?
Answer
Lexical scope refers to the definition space — not where something is executed or called. Since we defined myName in the global environment, its lexical scope is the global scope. The function getName() simply accesses it; it doesn’t change where myName “lives.”
Why this matters:
- The scope chain is determined by the static structure of your code—not by the runtime call stack.
- Even if you call
getName()from deep inside other functions, it still refers back to the globalmyName.
Example 2
function getName() {
const myName = "Oluwatobi";
return myName;
}
getName(); // returns "Oluwatobi"
Question:
Where is myName's lexical scope? Is it in the global scope or in the local scope of getName()?
Answer:
Since myName was declared inside the function getName(), its lexical scope is getName()’s local environment. This is the definition space where it was created.
Why This Matters
- The function
getName()has its own local environment (lexical scope), wheremyNamelives and can be accessed. - No code outside of
getName()can accessmyNamedirectly, even thoughgetName()returns its value. - Lexical scope is always based on the structure of your code (definition location), not where you call or invoke it.
Example 3
function outer() {
const outerVal = 'outer';
function inner() {
console.log(outerVal);
}
inner(); // logs 'outer'
}
outer();
Note : inner() looks for outerVal. It first checks its own scope (inside inner) — not found. Next it checks the parent lexical environment (inside outer) — found 'outer'. Even if inner() is called inside outer, it's defined inside outer, so it can access outerVal by lexical scope.
Example 4
const globalName = 'global';
function greet() {
console.log(globalName);
}
function innerWrapper() {
const globalName = 'local inside';
greet(); // logs 'global', not 'local inside'
}
innerWrapper();
Note : greet() is declared in the global scope. Even though it's called inside innerWrapper, it still refers to the global globalName. That's because lexical scope is determined by where greet was written, not where it is called