Understanding how JavaScript handles variables is crucial for mastering the language. This post walks through the differences between var, let, and const, the concept of scope, variable mutability, and concludes with an exploration of closures.
1. var vs let vs const: What’s the Difference?
Global Scope Behavior
When declared outside of any function or block, all three var, let, and const are globally accessible. However, their behavior differs:
- var: Function-scoped or globally scoped if declared outside any function. It is hoisted and initialized with undefined.
- let: Block-scoped. It is hoisted but not initialized, leading to a "Temporal Dead Zone" (TDZ) until the declaration is encountered.
- const: Block-scoped. Like let, it is hoisted but not initialized. Additionally, it must be assigned a value at the time of declaration and cannot be reassigned later.
2. Understanding Scope
Scope determines the accessibility of variables. JavaScript has three types of scope:
- Global Scope: Variables declared outside any function or block.
- Function Scope: Variables declared within a function.
- Block Scope: Variables declared within a block (e.g., inside {}).
Global Scope Example:
let myPodcastName = "ExtendWomenInTech";
function getPodcastName() {
console.log(myPodcastName); // ✅ "ExtendWomenInTech"
}
getPodcastName();
console.log(myPodcastName); // ✅ "ExtendWomenInTech"
Function Scope Example:
function getPodcastName() {
let myPodcastName = "ExtendWomenInTech";
console.log(myPodcastName); // ✅ "ExtendWomenInTech"
}`
getPodcastName();
console.log(myPodcastName); // ❌ ReferenceError: myPodcastName is not defined
Block Scope Example:
{
let topic = "Learning Scope";
console.log(topic); // ✅ "Learning Scope"
}
console.log(topic); // ❌ ReferenceError: topic is not defined
3. Assignment & Re-assignment
- let and var allow both reassignment and mutation of variables.
- const prevents reassignment of the variable itself but allows mutation of the value if it's an object.
let message = "Hello outside";
function showMessage() {
let message = "Hello inside";
console.log(message); // ✅ "Hello inside"
message = "Hello changed inside";
console.log(message); // ✅ "Hello changed inside"
}
showMessage();
console.log(message); // ✅ "Hello outside"
Object Mutation with const:
const user = { name: "Alice" };
function updateUser() {
user.name = "Tsedey"; // ✅ Mutating property
return user.name;
}
console.log(updateUser()); // ✅ "Tsedey"
console.log(user); // ✅ { name: "Tsedey" }
Attempting to reassign the entire object will result in an error:
// ❌ TypeError: Assignment to constant variable.
// user = { name: "Charlie" };
4. var vs let vs const in Re-assignment
var nameVar = "Alice";
let nameLet = "Bob";
const nameConst = "Carol";
function updateNames() {
nameVar = "Updated by var"; // ✅ works
nameLet = "Updated by let"; // ✅ works
// nameConst = "Updated by const"; // ❌ TypeError
}
updateNames();
console.log(nameVar); // ✅ "Updated by var"
console.log(nameLet); // ✅ "Updated by let"
console.log(nameConst); // ✅ "Carol"
5. Scopes, Shadowing & Re-assignment
var globalVar = "I am global var";
let globalLet = "I am global let";
function scopeTest() {
var funcVar = "I am func var";
let funcLet = "I am func let";
console.log(globalVar); // ✅ Accessible
console.log(globalLet); // ✅ Accessible
{
var blockVar = "I am block var"; // function-scoped
let blockLet = "I am block let"; // block-scoped
console.log(funcVar); // ✅ Accessible
console.log(funcLet); // ✅ Accessible
console.log(blockVar); // ✅ Accessible
console.log(blockLet); // ✅ Accessible
blockLet = "Changed block let";
console.log(blockLet); // ✅ "Changed block let"
}
console.log(blockVar); // ✅ Accessible
// console.log(blockLet); // ❌ ReferenceError
}
scopeTest();
console.log(globalVar); // ✅ "I am global var"
console.log(globalLet); // ✅ "I am global let"
6. Hoisting: The Sneaky Part
Hoisting refers to JavaScript's behavior of moving declarations to the top of their containing scope during the compile phase.
console.log(a); // ✅ undefined
var a = 10;
console.log(b); // ❌ ReferenceError: Cannot access 'b' before initialization
let b = 20;
console.log(c); // ❌ ReferenceError: Cannot access 'c' before initialization
const c = 30;
- var: Hoisted and initialized with undefined.
- let and const: Hoisted but not initialized, leading to a Temporal Dead Zone (TDZ) until the declaration is encountered.
7.Closures: Functions with Memory
A closure is a function that "remembers" its lexical scope, even when the function is executed outside that scope.
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const counter = outer();
counter(); // ✅ 1
counter(); // ✅ 2
counter(); // ✅ 3
In this example, the inner function forms a closure that retains access to the count variable from its lexical scope, even after the outer function has finished executing.
💬 Final Thought
Understanding how scope, reassignment, and hoisting work under the hood will make debugging and structuring your code far easier. When in doubt, use const by default, switch to let when you need reassignment, and avoid var unless you're debugging legacy code.
Top comments (0)