JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
156: The Module Pattern
Up until now, we've been writing functions and variables that mostly live in the global scope or are passed around explicitly. But in a real-world project, the global scope is a dangerous place. If you have a variable called score and some third-party library you imported also uses a variable called score, you're going to have a bad time. This is why we use the Module Pattern.
Essentially, we're using closures to create "private" variables that can't be touched from the outside, while selectively exposing a public API. Let's build a GameStats module to handle a player's score and level.
Wrapping our state in a closure
I'll start by using an IIFE (Immediately Invoked Function Expression). This allows me to create a private scope immediately. I want the score and level to be completely inaccessible to anyone who just wants to cheat by typing gameStats.score = 9999 in the browser console.
const GameStats = (function() {
let score = 0;
let level = 1;
// I'll put my logic here
})();
Right now, this is just a function that runs once and returns nothing. The variables score and level exist only while that function is executing, and then they vanish. To make them useful, we need to return an object containing functions that "remember" these variables via closure.
Defining how we actually interact with the score
I don't want the outside world to set the score directly, but I do want them to be able to increase it. I'll add a method to handle the incrementing and another to simply read the value.
const GameStats = (function() {
let score = 0;
let level = 1;
return {
addPoints: function(points) {
score += points;
console.log(`Points added! Current score: ${score}`);
},
getScore: function() {
return score;
}
};
})();
Now, if I call GameStats.addPoints(10), it works perfectly. But if I try to console.log(GameStats.score), I get undefined. That's exactly what we want. The data is encapsulated.
Fixing the "undefined" mistake
Here is where I usually trip up when I'm rushing. I wanted to add a feature to track the player's level, so I added a function to increment the level, but I forgot to actually add that function to the returned object. Let's look at my mistake:
const GameStats = (function() {
let score = 0;
let level = 1;
function levelUp() {
level++;
console.log(`Leveled up to ${level}!`);
}
return {
addPoints: function(points) {
score += points;
},
getScore: function() {
return score;
}
// I forgot to put levelUp in here!
};
})();
When I try to call GameStats.levelUp(), JavaScript throws a TypeError because levelUp is private to the IIFE. It exists inside the closure, but it's not part of the public API. To fix this, I have to explicitly map that internal function to a property on the returned object.
const GameStats = (function() {
let score = 0;
let level = 1;
function levelUp() {
level++;
console.log(`Leveled up to ${level}!`);
}
return {
addPoints: function(points) {
score += points;
},
getScore: function() {
return score;
},
levelUp: levelUp // Now it's public
};
})();
Adding a reset mechanism
To wrap this up, I'll add a way to reset the game. This is the beauty of the module pattern: I can provide a controlled way to manipulate the private state without ever exposing the state variables themselves. I'll add a reset method that sets everything back to zero and one.
const GameStats = (function() {
let score = 0;
let level = 1;
function levelUp() {
level++;
}
function reset() {
score = 0;
level = 1;
console.log("Game reset.");
}
return {
addPoints: (pts) => score += pts,
getScore: () => score,
levelUp: levelUp,
reset: reset
};
})();
Notice I switched to arrow functions for the simpler getters/setters—it keeps the code cleaner. We've now built a secure, self-contained module that protects its own internal state.
📋 Practical Task
Build a Private BankAccount Module
Create a module named BankAccount using the Module Pattern (IIFE). The module should meet the following requirements:
- Maintain a private variable
balanceinitialized to 0. - Expose a
depositmethod that adds a positive amount to the balance. - Expose a
withdrawmethod that subtracts an amount from the balance, but only if the balance is sufficient (otherwise, it should log "Insufficient funds"). - Expose a
getBalancemethod that returns the current balance.
Ensure that the balance variable cannot be accessed or modified directly from outside the module.
There are no comments for now.