JavaScript Scope & Execution Context ๐ฏ
JavaScript Scope & Execution Context ๐ฏ
Part 1: Scope - Where Variables Live ๐
What is Scope?
Scope determines where variables can be accessed in your code.
Think of it like rooms in a house:
- You can see what's in your current room
- You can see into rooms that contain your room
- You can't see into smaller rooms inside your room
Types of Scope
1. Global Scope ๐
Variables accessible everywhere in your code.
// Global scope - like the entire house const planet = "Earth"; const population = 8000000000; function showPlanet() { console.log(planet); // โ Can access global } if (true) { console.log(planet); // โ Can access global } console.log(planet); // โ Can access global
When to use:
- Constants used throughout your app
- Configuration values
- But be careful! Too many globals = messy code
2. Function Scope ๐ข
Variables only accessible inside the function where declared.
function calculateTax() { // Function scope - like a private office const taxRate = 0.15; var oldStyleTax = 0.10; // var is also function scoped const total = 1000 * taxRate; console.log(total); // โ Works inside function return total; } calculateTax(); // Works // Outside the function: console.log(taxRate); // โ Error: taxRate is not defined console.log(total); // โ Error: total is not defined console.log(oldStyleTax); // โ Error: oldStyleTax is not defined
Key point: Both var and let/const are function scoped!
3. Block Scope ๐ฆ
Variables only accessible inside {} blocks (with let and const only).
if (true) { // Block scope - like a small cubicle let blockVar = "I'm trapped in this block"; const alsoBlock = "Me too"; var notBlock = "I escape blocks!"; console.log(blockVar); // โ Works inside block } // Outside the block: console.log(blockVar); // โ Error: not defined console.log(alsoBlock); // โ Error: not defined console.log(notBlock); // โ Works! (var ignores block scope)
Block scope works with:
ifstatementsforloopswhileloops- Any
{...}curly braces
// Block scope in loops for (let i = 0; i < 3; i++) { // 'i' only exists inside this loop console.log(i); // 0, 1, 2 } console.log(i); // โ Error: i is not defined // Block scope in random blocks { const secret = "Hidden"; console.log(secret); // โ Works } console.log(secret); // โ Error
Scope Chain ๐
JavaScript searches for variables by going up the chain from inner to outer scopes.
// Level 1: Global scope const level1 = "Global"; function outer() { // Level 2: Outer function scope const level2 = "Outer"; function inner() { // Level 3: Inner function scope const level3 = "Inner"; // JavaScript searches: inner โ outer โ global console.log(level3); // โ Found in inner (current scope) console.log(level2); // โ Found in outer (parent scope) console.log(level1); // โ Found in global (grandparent scope) } inner(); // Can't access inner scope from outer console.log(level3); // โ Error - can't go DOWN the chain } outer();
How Scope Chain Works:
- JavaScript looks in current scope first
- If not found โ looks in parent scope
- Keeps going up until global scope
- If still not found โ Error: not defined
Visual Representation:
Global Scope (level1) ๐
โโโ Outer Function (level2) ๐ข
โโโ Inner Function (level3) ๐ฆ
Search direction: ๐ฆ โ ๐ข โ ๐ (inner to outer)
NEVER: ๐ โ ๐ข โ ๐ฆ (outer to inner)
Lexical Scope (Static Scope) ๐
Scope is determined by WHERE you write the code, not WHERE you call it.
const name = "Global Alice"; function outer() { const name = "Outer Bob"; function inner() { // 'name' is determined by WHERE inner() is WRITTEN // inner() is written inside outer(), so it uses outer's 'name' console.log(name); } return inner; } const name = "Global Charlie"; // This doesn't affect inner() const myFunc = outer(); myFunc(); // Prints "Outer Bob" (NOT "Global Charlie")
Why this matters:
- Functions "remember" where they were defined
- This is the foundation of closures
- Makes code predictable and easier to debug
Shadowing Variables ๐
When inner scope has same variable name as outer scope:
const message = "Global message"; function showMessage() { const message = "Function message"; // Shadows global 'message' console.log(message); // "Function message" (uses closest scope) if (true) { const message = "Block message"; // Shadows function 'message' console.log(message); // "Block message" } console.log(message); // "Function message" again } showMessage(); console.log(message); // "Global message"
Rule: JavaScript always uses the closest scope when variables have same name.
Part 2: Execution Context - The Environment โ๏ธ
What is Execution Context?
Execution Context = The environment where JavaScript code is executed.
Think of it as a workspace that JavaScript creates:
- Stores variables
- Tracks function calls
- Manages scope
Types of Execution Context
1. Global Execution Context ๐
Created when your script first runs. There's only ONE global context.
// This code runs in Global Execution Context const globalVar = "I'm global"; let count = 0; function myFunc() { // This will create a Function Execution Context } // Still in Global Execution Context console.log(globalVar);
Global Context contains:
- Global variables
- Global functions
windowobject (in browsers)this= window (in browsers)
2. Function Execution Context ๐ญ
Created every time a function is called. Destroyed when function finishes.
function greet(name) { // New execution context created when called const greeting = "Hello"; console.log(greeting + " " + name); // Context destroyed after this line } greet("Alice"); // Creates context #1, then destroys it greet("Bob"); // Creates context #2, then destroys it greet("Charlie"); // Creates context #3, then destroys it
Each function context contains:
- Function's local variables
- Function's parameters
- Reference to outer scope (scope chain)
thisvalue
Execution Context Phases ๐
Every execution context goes through 2 phases:
Phase 1: Creation Phase (Memory Setup) ๐ง
JavaScript scans code and sets up memory before executing:
// Creation Phase happens BEFORE this code runs console.log(x); // undefined (not an error!) console.log(greet); // [Function: greet] console.log(y); // โ ReferenceError: Cannot access 'y' before initialization var x = 10; function greet() { return "Hello"; } let y = 20;
What happens in Creation Phase:
| Declaration Type | What Happens | Can Access Before Declaration? |
|---|---|---|
var | Hoisted, set to undefined | โ Yes (but value is undefined) |
function declaration | Hoisted completely | โ Yes (fully works) |
let / const | Hoisted but not initialized | โ No (Temporal Dead Zone) |
function expression | Follows variable rules | Depends on var/let/const |
Hoisting Example:
// What you write: console.log(a); // undefined var a = 5; sayHi(); // "Hi!" function sayHi() { console.log("Hi!"); } // What JavaScript does internally: var a; // Hoisted to top, value = undefined function sayHi() { // Hoisted completely console.log("Hi!"); } console.log(a); // undefined a = 5; sayHi(); // "Hi!"
Temporal Dead Zone (TDZ):
// TDZ for 'x' starts here console.log(x); // โ Error: Cannot access before initialization let x = 10; // TDZ ends here console.log(x); // โ Works: 10
Phase 2: Execution Phase (Code Runs) โถ๏ธ
JavaScript executes code line by line:
// Execution Phase - runs top to bottom var x = 10; // x gets value 10 let y = 20; // y gets value 20 const z = 30; // z gets value 30 function calculate() { const result = x + y + z; return result; } console.log(x); // 10 console.log(y); // 20 console.log(calculate()); // 60
Call Stack ๐
JavaScript uses a Call Stack to manage execution contexts.
Call Stack = Stack of execution contexts (LIFO - Last In, First Out)
function first() { console.log("Inside first"); second(); console.log("Back in first"); } function second() { console.log("Inside second"); third(); console.log("Back in second"); } function third() { console.log("Inside third"); } first();
Call Stack Visualization:
Step 1: Script starts
[Global Execution Context]
Step 2: first() called
[Global Execution Context]
[first() Execution Context] โ Current
Step 3: second() called inside first()
[Global Execution Context]
[first() Execution Context]
[second() Execution Context] โ Current
Step 4: third() called inside second()
[Global Execution Context]
[first() Execution Context]
[second() Execution Context]
[third() Execution Context] โ Current
Step 5: third() finishes
[Global Execution Context]
[first() Execution Context]
[second() Execution Context] โ Back here
Step 6: second() finishes
[Global Execution Context]
[first() Execution Context] โ Back here
Step 7: first() finishes
[Global Execution Context] โ Back to global
Step 8: Script ends
[Empty]
Output:
Inside first
Inside second
Inside third
Back in second
Back in first
Practical Example: Bank Account ๐ฆ
// Global Execution Context let globalBalance = 1000; function createBankAccount(customerName) { // createBankAccount Execution Context let balance = globalBalance; // Local copy let transactions = []; function deposit(amount) { // deposit Execution Context let timestamp = new Date(); // Can access (via scope chain): // - timestamp (own scope) // - amount (parameter) // - balance (parent scope) // - transactions (parent scope) // - customerName (parent scope) // - globalBalance (global scope) balance += amount; transactions.push({ type: "Deposit", amount: amount, time: timestamp }); console.log(`${customerName} deposited $${amount}`); console.log(`New balance: $${balance}`); } function withdraw(amount) { // withdraw Execution Context let timestamp = new Date(); if (balance >= amount) { balance -= amount; transactions.push({ type: "Withdraw", amount: amount, time: timestamp }); console.log(`${customerName} withdrew $${amount}`); console.log(`New balance: $${balance}`); } else { console.log("Insufficient funds!"); } } function getStatement() { console.log(`Account holder: ${customerName}`); console.log(`Current balance: $${balance}`); console.log("Transactions:", transactions); } return { deposit, withdraw, getStatement }; } // Using the bank account const aliceAccount = createBankAccount("Alice"); aliceAccount.deposit(500); // Creates deposit() execution context aliceAccount.withdraw(200); // Creates withdraw() execution context aliceAccount.getStatement(); // Creates getStatement() execution context // โ Can't access these from outside: console.log(balance); // Error: not defined console.log(transactions); // Error: not defined console.log(customerName); // Error: not defined
When and Why to Use This Knowledge? ๐ค
1. Avoiding Variable Bugs ๐
BAD - var in loops:
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Prints: 3, 3, 3 (all share same 'i')
GOOD - let in loops:
for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Prints: 0, 1, 2 (each iteration has its own 'i')
2. Creating Private Variables ๐
function createCounter() { let count = 0; // Private - can't access from outside return { increment: () => ++count, decrement: () => --count, getCount: () => count }; } const counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.count); // undefined (private!)
3. Understanding Hoisting Issues โฌ๏ธ
// Why this works: sayHi(); // "Hi!" function sayHi() { console.log("Hi!"); } // Why this doesn't: greet(); // โ Error: greet is not a function var greet = function() { console.log("Hello!"); }; // Because var greet is hoisted as undefined
4. Debugging Scope Errors ๐ง
function outer() { let x = 10; function inner() { console.log(x); // โ Works (scope chain) console.log(y); // โ Error (not in scope chain) } let y = 20; inner(); }
Understanding scope helps you:
- Know where variables are accessible
- Fix "undefined" vs "not defined" errors
- Read call stack in debugger
- Write cleaner, bug-free code
Key Takeaways ๐
Scope:
- Global Scope = everywhere accessible
- Function Scope = only inside function
- Block Scope = only inside
{}(let/const only) - Scope Chain = searches inner โ outer
- Lexical Scope = determined by code position
Execution Context:
- Global Context = created when script starts
- Function Context = created on function call
- Creation Phase = memory setup (hoisting)
- Execution Phase = code runs line by line
- Call Stack = manages execution contexts
Best Practices:
- โ
Use
letandconst(block scoped) - โ Avoid
var(function scoped, confusing hoisting) - โ Declare variables at top of their scope
- โ Use scope for encapsulation/privacy
- โ Understand scope chain for debugging
Memory Trick ๐ง
Scope = Russian Nesting Dolls ๐ช
- Inner dolls can see outer dolls
- Outer dolls can't see inside inner dolls
- JavaScript always looks inside โ outside (NEVER outside โ inside)
Execution Context = Workspace ๐ ๏ธ
- Creation Phase = Setting up the workspace
- Execution Phase = Actually doing the work
- Call Stack = Stack of workspaces