JavaScript Closures Tutorial - Section 1: Fundamentals
A closure is one of JavaScript's most powerful and misunderstood features. Simply put, a closure is a function that has access to variables from an outer (enclosing) function's scope, even after that outer function has finished executing.
Before we dive into closures, we need to understand scope - the context in which variables are accessible.
Scope determines where variables can be accessed in your code. JavaScript has three types of scope:
Variables declared outside any function are in the global scope and can be accessed anywhere:
const globalVar = "I'm global!";
function showGlobal() {
console.log(globalVar); // Can access global variable
}
showGlobal(); // Output: "I'm global!"
console.log(globalVar); // Output: "I'm global!"
Variables declared inside a function are only accessible within that function:
function myFunction() {
const localVar = "I'm local!";
console.log(localVar); // Works fine
}
myFunction(); // Output: "I'm local!"
console.log(localVar); // Error: localVar is not defined
Variables declared with let or const inside a block {} are only accessible within that block:
if (true) {
let blockVar = "I'm in a block!";
const anotherBlockVar = "Me too!";
console.log(blockVar); // Works fine
}
console.log(blockVar); // Error: blockVar is not defined
Now that we understand scope, let's see a closure in action:
function outerFunction() {
const outerVar = "I'm from outer!";
function innerFunction() {
console.log(outerVar); // Can access outerVar
}
return innerFunction;
}
const myClosure = outerFunction();
myClosure(); // Output: "I'm from outer!"
What just happened? Let's break it down:
outerFunction declares a variable outerVarinnerFunction is defined inside outerFunctioninnerFunction references outerVar from its parent scopeouterFunction returns innerFunctionouterFunction() and store the returned function in myClosuremyClosure(), it still has access to outerVar!
This is a closure! Even though outerFunction has finished executing, innerFunction still "remembers" and can access outerVar.
Closures exist because of JavaScript's lexical scoping. When a function is created, it retains a reference to its lexical environment (the scope in which it was created). This means the function can access variables from that environment, even if the outer function has completed.
Let's see a more practical use of closures - creating a counter:
function createCounter() {
let count = 0; // Private variable
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// Can't access count directly
console.log(counter.count); // undefined
This is powerful! The count variable is private - it can only be modified through the methods we've provided. This is called data encapsulation, and it's one of the most common uses of closures.
Now that you understand what closures are and how scope works, in the next lesson we'll dive deeper into lexical scope and the scope chain to understand exactly how JavaScript resolves variable references.