JavaScript Lexical Scope and Compilation
Part 2 of the JavaScript Deep Dive Series
Before JavaScript executes a single line of your code, something crucial happens behind the scenes. While most developers think JavaScript executes and runs code line by line(I know I mentioned this in the first series), the reality is more sophisticated and understanding this process is the key to mastering scope, hoisting, and variable behavior. Javascript still executes code line by line, but before that it interprets/compiles the javascript code.
When you write a javascript code, the first thing that happens is what I refer to as “Parsing” also can be referred to as “Compilation” or interpretation.
A relatable example is when you have a syntax error in your code on line 20, but immediately you recompile, you get an error, when infact your code hasn’t executed up to line 20, the reason why this error shows up immediately is because of the first step “Parsing/Compilation”, during this phase, Javascript engines runs through the code, define variables, verify syntaxes, etc.
So, your code is first compiled, then executed, and that brings us to “The Two step process”.
JavaScript's Two-Step Process
Javascript follows a two-step process:
Compilation Phase - JavaScript scans through your entire code, setting up scope and preparing for execution
Execution Phase - The actual running of your code, line by line
This compilation step is where all the "magic" of hoisting, scope creation, and variable declarations happens.
Definitions
Before we proceed into JavaScript's compilation process, let's explain a few terms you will come accross:
Scope - The accessibility of variables and functions in different parts of your code. It determines where you can use a particular variable.
Lexical Scope - Scope that is determined by where variables and functions are declared in your code (at write-time), not where they are called from (at runtime).
Global Scope - The outermost scope in JavaScript. Variables declared here are accessible from anywhere in your program(more like in the window).
Function Scope - A scope created inside a function. Variables declared here are only accessible within that function.
Block Scope - A scope created by curly braces {} when used with let or const. Variables declared here are only accessible within that block.
Source Reference - When a variable is being read or accessed (appears on the right-hand side of an assignment or in expressions like console.log(variable)).
Target Reference - When a variable is being assigned a value (appears on the left-hand side of an assignment like variable = "value").
Compilation Phase - The first step where JavaScript scans through your code, sets up scope, and prepares variable declarations before any code executes.
Execution Phase - The second step where JavaScript actually runs your code line by line.
Scope Chain - The hierarchy of scopes that JavaScript searches through when looking up a variable, starting from the innermost scope and moving outward.
Hoisting - The behavior where variable and function declarations are processed during the compilation phase, making them available throughout their scope even before their declaration line is reached during execution.
Step 1: Compilation - The Setup Phase
During compilation, JavaScript's engine acts like an organizer, scanning through your code and asking two critical questions about every variable it encounters:
"What scope does this belong to?"
"Is this a source or target reference?"
var teacher = "Kyle";
function otherClass() {
teacher = "Suzy";
topic = "React";
console.log("Welcome!");
}
otherClass();
Let's trace through the compilation phase:
Line 1: var teacher = "Kyle"
Compiler finds
teacherdeclarationCreates
teacherin global scopeNotes: This will be a target reference (receiving a value)
Line 3: function otherClass()
Compiler creates
otherClassfunction in global scopeCreates new scope for inside the function
Line 4: teacher = "Suzy"
Compiler sees
teacherreferenceNo declaration here, so it's a source reference (looking up existing variable)
Notes: Will look up scope chain during execution
Line 5: topic = "React"
Compiler sees
topicreferenceNo declaration anywhere - this will create an implicit global
Source vs Target References
Understanding the difference between source and target references is crucial:
Target Reference = Left-hand side of assignment (receiving a value)
var student = "Frank"; // student is TARGET
teacher = "Kyle"; // teacher is TARGET
Source Reference = Right-hand side of assignment (providing a value)
console.log(teacher); // teacher is SOURCE
var message = teacher; // teacher is SOURCE, message is TARGET
Why This Matters
The engine handles source and target references completely differently:
Target references: Must have a declared variable to assign to Source references: Must find an existing variable to read from
// Compilation phase creates these declarations
var teacher; // TARGET reference prepared
var student; // TARGET reference prepared
// Execution phase
teacher = "Kyle"; // TARGET - assigns to existing declaration
console.log(teacher); // SOURCE - looks up existing value
topic = "React"; // TARGET - no declaration found, creates global
console.log(subject); // SOURCE - no declaration found, ReferenceError!
Lexical Scope - Where You Write Matters
Lexical scope means scope is determined by where you write your code, not where you call it. The physical placement of your variables and functions determines their scope relationships.
var teacher = "Kyle";
function otherClass() {
var teacher = "Suzy";
function ask() {
console.log(teacher); // Which teacher?
}
ask();
}
otherClass(); // "Suzy"
The ask() function looks for teacher in this order:
Local scope (inside
ask) - not foundEnclosing scope (inside
otherClass) - found "Suzy"!Global scope - never reaches here
This lookup happens at compile time based on where you wrote the code, not where you called it.
Scope Chain Resolution
JavaScript creates a scope chain during compilation - a linked list of scopes from innermost to outermost:
var global = "I'm global";
function outer() {
var outerVar = "I'm in outer";
function inner() {
var innerVar = "I'm in inner";
console.log(innerVar); // Found in inner scope
console.log(outerVar); // Found in outer scope
console.log(global); // Found in global scope
console.log(missing); // ReferenceError!
}
inner();
}
outer();
Scope chain for inner() function: Inner Scope → Outer Scope → Global Scope
The engine walks up this chain until it finds the variable or reaches the end (ReferenceError).
The Compilation Process in Action
Let's trace through a complete example:
console.log(teacher); // What happens here?
var teacher = "Kyle";
function ask() {
console.log(teacher);
}
ask();
Compilation Phase:
Line 3: Create
teacherdeclaration in global scopeLine 5: Create
askfunction in global scopeLine 6: Create new scope for
askfunctionAll source references noted for execution phase
Execution Phase:
Line 1:
console.log(teacher)- SOURCE reference toteacherteacherexists (from compilation) but value isundefinedOutput:
undefined
Line 3:
teacher = "Kyle"- TARGET reference- Assigns "Kyle" to existing
teacherdeclaration
- Assigns "Kyle" to existing
Line 8:
ask()- Calls functionLine 6:
console.log(teacher)- SOURCE referenceLooks up
teacherin scope chain, finds "Kyle"Output: "Kyle"
Common Scope Misconceptions
"Hoisting moves declarations to the top" False. Nothing physically moves. Compilation creates declarations before execution begins.
"let and const aren't hoisted"
False. They are hoisted but in a "temporal dead zone" until their line is reached.
"Functions create scope" Partially true. Function declarations create scope, but so do blocks with let/const.
"Scope is determined at runtime" False. Lexical scope is determined at compile time by where you write code.
Practical Applications
Understanding compilation and lexical scope helps you:
Debug Variable Access Issues
function broken() {
console.log(name); // ReferenceError - not undefined!
let name = "Frank";
}
Predict Hoisting Behavior
console.log(typeof teacher); // "undefined" (var hoisting)
console.log(typeof student); // ReferenceError (let in TDZ)
var teacher = "Kyle";
let student = "Frank";
Understand Closure Creation
function makeCounter() {
var count = 0;
return function() {
return ++count; // Lexical scope preserves access to count
};
}


