javascript-course-chapter-15
Chapter 15: Closures
Learn JavaScript closures with explanations, fully commented code, outputs, and code explanations.
Goal: Understand how functions remember variables from outer scopes and how closures are used in real JavaScript projects.
15.1 Closure Basics
A closure happens when an inner function remembers variables from an outer function.
The outer function can finish running, but the inner function can still use its variables.
JavaScript keeps those needed variables alive in memory.
Closures are very important in modern JavaScript.
They are used in counters, buttons, callbacks, modules, and state management.
Beginners can think of a closure as a function that remembers where it was created.
Example: Basic closure
// Create an outer function.
function outerFunction() {
// Create a variable inside the outer function.
let message = "Hello from closure";
// Create an inner function.
function innerFunction() {
// Inner function remembers the outer variable.
console.log(message);
}
// Return the inner function.
return innerFunction;
}
// Store the returned inner function.
let savedFunction = outerFunction();
// Call the saved function.
savedFunction();
Output:Hello from closure
Code Explanation: The variable message is created inside outerFunction(). The inner function uses that variable. Even after outerFunction() finishes, the returned function still remembers message. This remembered access is called a closure.
15.2 Lexical Environment
A lexical environment is where JavaScript stores variables and functions while code runs.
Every function creates its own lexical environment.
Inner functions can access variables from outer lexical environments.
This is one of the main reasons closures work.
JavaScript decides this access based on where the function is written.
Beginners should understand lexical environment before learning advanced closure patterns.
Example: Inner function uses lexical environment
// Create an outer function.
function createGreeting() {
// Store a variable in the outer lexical environment.
let greeting = "Welcome";
// Return an inner function.
return function(name) {
// Use outer variable and parameter together.
console.log(greeting + ", " + name);
};
}
// Store returned function.
let greetUser = createGreeting();
// Call returned function.
greetUser("Sara");
Code Explanation: The variable greeting is inside createGreeting(). The returned inner function remembers that lexical environment. When greetUser("Sara") runs later, it still has access to greeting. This is how lexical environment supports closures.
15.3 Private Variables
Closures can create private variables in JavaScript.
A private variable cannot be accessed directly from outside the function.
Only functions created inside the same scope can use it.
This protects important data from accidental changes.
Private variables are useful for counters, settings, passwords, and internal state.
Beginners can use closures to hide data and expose only safe actions.
Example: Private counter
// Create a function that protects count.
function createCounter() {
// Private variable.
let count = 0;
// Return an object with a safe method.
return {
increment: function() {
// Change private variable safely.
count++;
// Print updated value.
console.log(count);
}
};
}
// Create counter object.
let counter = createCounter();
// Use public method.
counter.increment();
counter.increment();
Code Explanation: The variable count is created inside createCounter(). Outside code cannot directly access it. The method increment() can access it because of closure. This makes count act like private data.
15.4 Factory Functions
A factory function is a function that creates and returns objects.
Closures can help factory functions remember private values.
Each object created by the factory can have its own remembered data.
This makes factory functions useful for creating many similar objects.
They are often easier for beginners than classes at first.
Factory functions help create reusable and organized JavaScript code.
Example: Student factory
// Create a factory function.
function createStudent(name) {
// Private value remembered by closure.
let studentName = name;
// Return an object with a method.
return {
showName: function() {
// Access private value.
console.log(studentName);
}
};
}
// Create two students.
let student1 = createStudent("Ali");
let student2 = createStudent("Sara");
// Call methods.
student1.showName();
student2.showName();
Code Explanation: The function createStudent() creates a private variable called studentName. Each returned object has a method that remembers its own value. student1 remembers Ali. student2 remembers Sara.
15.5 Module Pattern
The module pattern uses closures to organize code into private and public parts.
Private variables stay hidden inside a function.
Public methods are returned so other code can use them.
This pattern helps avoid creating too many global variables.
It also keeps related code grouped together.
Beginners can use the module pattern to make code cleaner and safer.
Example: Simple module pattern
// Create a module using a function.
function createBankAccount() {
// Private balance variable.
let balance = 100;
// Return public methods.
return {
deposit: function(amount) {
// Add amount to private balance.
balance += amount;
// Show new balance.
console.log(balance);
}
};
}
// Create account module.
let account = createBankAccount();
// Use public method.
account.deposit(50);
Code Explanation: The variable balance is private inside createBankAccount(). The returned object exposes a public deposit() method. That method can change balance because of closure. Outside code cannot directly access balance.
15.6 Memory Considerations
Closures keep outer variables in memory when inner functions still need them.
This is useful, but it also means memory can stay used longer.
If a closure remembers large data, it may increase memory usage.
Good developers avoid storing unnecessary large values in closures.
When a closure is no longer needed, references should be removed.
Beginners should understand that closures remember data, so memory matters.
Example: Closure keeps data alive
// Create a function.
function createMessageHolder() {
// This value stays remembered by closure.
let message = "This message is remembered";
// Return inner function.
return function() {
// Use remembered message.
console.log(message);
};
}
// Store returned function.
let holder = createMessageHolder();
// Call closure.
holder();
Output:This message is remembered
Code Explanation: The variable message would normally disappear after the outer function finishes. The returned inner function still needs it. JavaScript keeps it in memory. This is useful, but large remembered data should be handled carefully.
15.7 Practical Examples
Closures are used in many practical JavaScript situations.
They help store values between function calls.
They can create counters, settings, timers, and reusable helpers.
Closures are common in real websites and web apps.
They allow functions to remember previous information.
Beginners should practice closures with small useful examples.
Example: Create a score tracker
// Create score tracker function.
function createScoreTracker() {
// Private score value.
let score = 0;
// Return function that updates score.
return function(points) {
// Add points to remembered score.
score += points;
// Print current score.
console.log("Score: " + score);
};
}
// Create tracker.
let addScore = createScoreTracker();
// Add points.
addScore(10);
addScore(5);
Output:Score: 10
Score: 15
Code Explanation: The variable score is remembered by the returned function. The first call adds 10. The second call adds 5 to the same remembered score. This shows how closures store values between function calls.
15.8 Event Handlers
Event handlers often use closures in browser JavaScript.
A function can remember values even after setup code finishes.
This is useful when buttons need to remember counts or settings.
Closures help event handlers keep their own data.
They are commonly used with clicks, keyboard input, forms, and timers.
Beginners will see closures often when learning DOM events.
Example: Button click counter idea
// Create a function for click counting.
function createClickCounter() {
// Private click count.
let clicks = 0;
// Return event handler function.
return function() {
// Increase remembered click count.
clicks++;
// Print clicks.
console.log("Clicks: " + clicks);
};
}
// Create handler.
let handleClick = createClickCounter();
// Simulate button clicks.
handleClick();
handleClick();
Output:Clicks: 1
Clicks: 2
Code Explanation: The function handleClick remembers the variable clicks. Every time it runs, it increases the same value. In a real webpage, this function could be used as a button click handler.
15.9 State Management
State means data that changes while a program runs.
Closures can store state safely inside functions.
This helps control how data is changed.
State management is important in counters, forms, games, carts, and apps.
Closures allow state to be private but still usable through functions.
Beginners can understand state management by practicing small closure examples.
Example: Manage login state
// Create state manager.
function createLoginState() {
// Private state.
let isLoggedIn = false;
// Return public methods.
return {
login: function() {
// Change private state.
isLoggedIn = true;
// Print state.
console.log("Logged in: " + isLoggedIn);
},
logout: function() {
// Change private state.
isLoggedIn = false;
// Print state.
console.log("Logged in: " + isLoggedIn);
}
};
}
// Create login state object.
let userState = createLoginState();
// Change state.
userState.login();
userState.logout();
Output:Logged in: true
Logged in: false
Code Explanation: The variable isLoggedIn is private. The methods login() and logout() can change it because they remember it through closure. This keeps state controlled and prevents random outside changes.
15.10 Best Practices
Closure best practices help keep code clean and easy to understand.
Use closures when you need to remember data privately.
Avoid using closures for data that does not need to be remembered.
Keep closure functions small and focused.
Avoid storing very large data in closures unless necessary.
Beginners should use clear names so closure code is easy to read.
Example: Clean closure function
// Create a clean counter closure.
function createCounter(start = 0) {
// Private count starts from parameter.
let count = start;
// Return one clear function.
return function() {
// Increase count.
count++;
// Return updated count.
return count;
};
}
// Create counter starting at 5.
let counter = createCounter(5);
// Print results.
console.log(counter());
console.log(counter());
Code Explanation: The function createCounter() creates a private count. The returned function has one clear job: increase and return count. The starting value is flexible because of the parameter. This is a clean and reusable closure pattern.
Chapter 15 Summary
- Closures allow inner functions to remember outer variables.
- Lexical environments store variables and support closure behavior.
- Private variables can be created with closures.
- Factory functions can create objects with remembered data.
- The module pattern uses closures for private and public code.
- Closures can keep data in memory longer.
- Practical closures are useful for counters, scores, and helpers.
- Event handlers often use closures to remember data.
- Closures can help manage private state.
- Best practices keep closures small, clear, and memory-friendly.