JavaScript Hoisting and Scope
Understanding variable hoisting and scope rules in JavaScript
JavaScript Hoisting and Scope
1. Hoisting
Definition
- Hoisting refers to the process by which variable and function declarations are moved to the top of their scope during the compilation (parsing) phase.
- In practice, the code does not physically move; instead, the JavaScript engine registers declarations in memory before executing code.
- This allows variables and functions to be accessed before their declaration appears in the source code.
Variable Hoisting
-
Variables declared with
var
:- Declarations are hoisted, but initializations are not.
- Accessing a
var
variable before initialization yieldsundefined
.
console.log(x); // undefined var x = 10;
-
Variables declared with
let
andconst
:- Also hoisted, but placed in a Temporal Dead Zone (TDZ) until their declaration is executed.
- Accessing them before initialization throws a ReferenceError.
console.log(y); // ReferenceError let y = 20;
Function Hoisting
- Functions declared with the
function
keyword are hoisted with both declaration and initialization. -
They can be called before their definition appears in the code.
greet(); // "Hello" function greet() { console.log("Hello"); }
- Internally, function declarations are fully initialized during hoisting, unlike variables which undergo separate declaration and initialization phases.
Why TDZ Exists
- ES6 introduced
let
andconst
with TDZ (Temporal Dead Zone) to improve code predictability. - Prevents accidental access of uninitialized variables.
- Increases safety by avoiding subtle bugs caused by hoisting.
Hoisting in the Execution Model
-
JavaScript is an interpreted language, but engines perform a parsing/compilation step before execution:
- Parsing Phase: Builds the AST (Abstract Syntax Tree) and creates the execution context. Declarations are registered in memory.
- Execution Phase: Code runs line by line. Variable initialization and function calls are executed.
-
Hoisting occurs during the parsing phase, which explains why declarations appear to “move up” in scope.
2. Scope
Definition
- Scope is the region of code where a variable or function is accessible.
- Determines the lifetime and visibility of variables.
- Accessing a variable outside its scope leads to a ReferenceError.
Types of Scope
-
Global Scope
- Variables declared outside any function/block are globally accessible.
-
Example:
var globalVar = "I am global";
-
Local Scope
- Created inside functions or blocks.
- Function Scope: Variables declared with
var
are scoped to the function. - Block Scope: Variables declared with
let
andconst
are scoped to the nearest{}
block.
Lexical (Static) Scope
- JavaScript uses lexical scoping: scope is determined by where the code is written, not by where it is executed.
-
Nested functions can access variables from their outer functions, but the reverse is not possible.
function outer() { let a = 10; function inner() { console.log(a); // 10 } inner(); } outer();
How Variable Resolution Works
- Every function invocation creates a new Execution Context.
-
Each execution context has a Lexical Environment:
- Environment Record: Stores local variable/function declarations.
- Outer Reference: Points to the parent scope’s lexical environment.
-
When a variable is referenced:
- The engine checks the current environment record.
- If not found, it follows the outer reference chain.
- If still not found, it checks the global environment.
- If missing, a ReferenceError is thrown.
3. Hoisting + Scope Together
- Hoisting determines when variables/functions are registered in memory.
- Scope determines where they are accessible.
-
Combined behavior:
- Variables with
var
are hoisted to their enclosing function/global scope and initialized withundefined
. let
/const
are hoisted but locked in the TDZ until initialization is reached in code.- Function declarations are hoisted with full initialization, available throughout their scope.
- Variables with
-
This explains why:
console.log(a); // undefined (var hoisting) var a = 1; console.log(b); // ReferenceError (TDZ) let b = 2; greet(); // Works (function hoisting) function greet() { console.log("hi"); }
4. Summary
- Hoisting: Declarations are processed during the parsing phase, before execution.
-
Variables:
var
→ hoisted, initialized toundefined
.let
/const
→ hoisted, but not initialized (TDZ applies).
- Functions: fully hoisted (declaration + initialization).
- Scope: Defines the visibility of variables (global, function, block).
- Lexical Scope: Variable scope is determined by the code’s written structure, not runtime.
- Execution Context: Each function creates a lexical environment with an environment record and an outer reference.