JavaScript Scope
Scope determines where variables are accessible in your code. Understanding scope is crucial for writing bug-free JavaScript.
What is Scope?
Scope is the context in which variables are declared and can be accessed. JavaScript has three types of scope:
- Global Scope
- Function Scope
- Block Scope
Global Scope
Variables declared outside any function or block are in the global scope:
// Global variable
let globalVar = 'I am global';
function showGlobal() {
console.log(globalVar); // Accessible
}
showGlobal(); // 'I am global'
console.log(globalVar); // 'I am global'
Warning: Avoid polluting the global scope!
Function Scope
Variables declared inside a function are only accessible within that function:
function myFunction() {
let functionVar = 'I am local';
console.log(functionVar); // Works
}
myFunction(); // 'I am local'
console.log(functionVar); // Error: functionVar is not defined
var has Function Scope
function testVar() {
if (true) {
var x = 10;
}
console.log(x); // 10 (var ignores block scope)
}
testVar();
Block Scope
Variables declared with let and const inside {} are block-scoped:
if (true) {
let blockVar = 'I am block-scoped';
const blockConst = 'Me too';
console.log(blockVar); // Works
}
console.log(blockVar); // Error: blockVar is not defined
let and const have Block Scope
function testLet() {
if (true) {
let x = 10;
const y = 20;
console.log(x, y); // 10, 20
}
console.log(x, y); // Error: not defined
}
Loops and Block Scope
// With let (block-scoped)
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // Error: i is not defined
// With var (function-scoped)
for (var j = 0; j < 3; j++) {
console.log(j); // 0, 1, 2
}
console.log(j); // 3 (still accessible!)
Nested Scope
Inner scopes can access outer scopes:
let global = 'global';
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(global); // Accessible
console.log(outerVar); // Accessible
console.log(innerVar); // Accessible
}
inner();
console.log(innerVar); // Error: not accessible
}
outer();
Scope Chain
JavaScript looks for variables from inner to outer scope:
let a = 'global a';
function outer() {
let b = 'outer b';
function inner() {
let c = 'inner c';
console.log(a); // Looks in inner → outer → global
console.log(b); // Looks in inner → outer
console.log(c); // Found in inner
}
inner();
}
outer();
Shadowing
Inner variables can "shadow" outer variables with the same name:
let name = 'Global';
function showName() {
let name = 'Local';
console.log(name); // 'Local' (shadows global)
}
showName();
console.log(name); // 'Global'
Hoisting
JavaScript moves declarations to the top of their scope:
var Hoisting
console.log(x); // undefined (not an error!)
var x = 5;
console.log(x); // 5
// Interpreted as:
// var x;
// console.log(x); // undefined
// x = 5;
// console.log(x); // 5
let and const Hoisting
console.log(y); // Error: Cannot access before initialization
let y = 10;
console.log(z); // Error: Cannot access before initialization
const z = 20;
Function Hoisting
// Function declarations are fully hoisted
sayHello(); // Works!
function sayHello() {
console.log('Hello!');
}
// Function expressions are NOT hoisted
sayGoodbye(); // Error!
const sayGoodbye = function() {
console.log('Goodbye!');
};
Closures
Functions remember their outer scope even after the outer function has returned:
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
Practical Closure Example
function createGreeting(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = createGreeting('Hello');
const sayHi = createGreeting('Hi');
console.log(sayHello('John')); // 'Hello, John!'
console.log(sayHi('Jane')); // 'Hi, Jane!'
Complete Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript Scope</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.demo {
background: #f5f5f5;
padding: 15px;
margin: 15px 0;
border-radius: 5px;
border-left: 4px solid #3498db;
}
button {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
margin: 5px;
cursor: pointer;
border-radius: 4px;
}
button:hover {
background: #2980b9;
}
.output {
background: white;
padding: 10px;
margin-top: 10px;
border-radius: 3px;
}
</style>
</head>
<body>
<h1>JavaScript Scope Demo</h1>
<div class="demo">
<h2>Global vs Local Scope</h2>
<button onclick="scopeDemo()">Run Demo</button>
<div id="scope-output" class="output"></div>
</div>
<div class="demo">
<h2>Block Scope (let vs var)</h2>
<button onclick="blockScopeDemo()">Run Demo</button>
<div id="block-output" class="output"></div>
</div>
<div class="demo">
<h2>Nested Scope</h2>
<button onclick="nestedScopeDemo()">Run Demo</button>
<div id="nested-output" class="output"></div>
</div>
<div class="demo">
<h2>Closure Example</h2>
<button onclick="createNewCounter()">Create Counter</button>
<button onclick="incrementCounter()">Increment</button>
<button onclick="resetDemo()">Reset</button>
<div id="closure-output" class="output">Counter: 0</div>
</div>
<script>
// Global variable
let globalMessage = 'I am global!';
function scopeDemo() {
let localMessage = 'I am local!';
let output = `
Global variable: ${globalMessage}<br>
Local variable: ${localMessage}<br>
<br>
Global is accessible everywhere.<br>
Local is only accessible in this function.
`;
document.getElementById('scope-output').innerHTML = output;
}
function blockScopeDemo() {
let output = '';
// let example
if (true) {
let blockLet = 'Block scoped with let';
output += `Inside block: ${blockLet}<br>`;
}
output += 'Outside block: blockLet is not accessible<br><br>';
// var example
if (true) {
var blockVar = 'Function scoped with var';
}
output += `Outside block: ${blockVar} (var ignores block scope)`;
document.getElementById('block-output').innerHTML = output;
}
function nestedScopeDemo() {
let outer = 'Outer scope';
function inner() {
let inner = 'Inner scope';
return `
Inner function can access:<br>
- Global: ${globalMessage}<br>
- Outer: ${outer}<br>
- Inner: ${inner}
`;
}
document.getElementById('nested-output').innerHTML = inner();
}
// Closure example
let counter = null;
function createNewCounter() {
counter = (function() {
let count = 0;
return function() {
count++;
return count;
};
})();
document.getElementById('closure-output').innerHTML = 'Counter: 0 (New counter created)';
}
function incrementCounter() {
if (counter === null) {
createNewCounter();
}
let value = counter();
document.getElementById('closure-output').innerHTML = `Counter: ${value}`;
}
function resetDemo() {
counter = null;
document.getElementById('closure-output').innerHTML = 'Counter: 0';
}
// Demonstrate hoisting
console.log('=== Hoisting Demo ===');
console.log('var before declaration:', typeof varVariable); // undefined
var varVariable = 'I am var';
try {
console.log('let before declaration:', letVariable);
} catch(e) {
console.log('let error:', e.message);
}
let letVariable = 'I am let';
</script>
</body>
</html>
Best Practices
- Use
letandconst- Avoidvar - Minimize global variables - Prevent naming conflicts
- Use block scope - Keep variables as local as possible
- Declare variables at the top - Improve readability
- Use closures wisely - Powerful but can cause memory leaks
Scope Summary
| Declaration | Scope | Hoisting | Reassignable |
|---|---|---|---|
| var | Function | Yes (undefined) | Yes |
| let | Block | Yes (not initialized) | Yes |
| const | Block | Yes (not initialized) | No |