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)
72: Static and Private Class Members
A few years back, I was reviewing a PR for a junior dev who was building a custom API wrapper. He wanted to keep track of the total number of requests sent across the entire application, so he added a requestCount property to his ApiClient class. The problem was, he defined it as a regular instance property. Every time he instantiated a new client, the count reset to zero for that specific object. He spent three hours debugging why his "global" counter was only ever returning 1, completely missing the fact that the data was tied to the instance, not the class itself.
That's the classic "aha!" moment for static members. Sometimes you need data or functionality that belongs to the blueprint itself, rather than the houses built from that blueprint.
Shared State with Static Members
When you mark a property or method as static, you're telling JavaScript: "This doesn't belong to any one specific object; it belongs to the class." You can't call a static method on an instance of the class—you call it directly on the class name.
class PaymentProcessor {
static transactionFee = 0.02; // 2% fee for everyone
static totalProcessed = 0;
process(amount) {
const finalAmount = amount + (amount * PaymentProcessor.transactionFee);
PaymentProcessor.totalProcessed += finalAmount;
return finalAmount;
}
static getGlobalVolume() {
return `Total volume: $${PaymentProcessor.totalProcessed}`;
}
}
const processorA = new PaymentProcessor();
const processorB = new PaymentProcessor();
processorA.process(100); // Total is now 102
processorB.process(200); // Total is now 306
console.log(PaymentProcessor.getGlobalVolume()); // "Total volume: $306"
// console.log(processorA.getGlobalVolume()); // TypeError: processorA.getGlobalVolume is not a function
Notice how totalProcessed keeps accumulating regardless of which instance is doing the work. I usually use static members for utility functions (like a User.compare(user1, user2) helper) or for configuration constants that should be identical for every single instance.
Encapsulation via Private Fields
For a long time, JavaScript developers used the "underscore convention" (like this._secret = 123) to signal that a property was private. The problem? It wasn't actually private. Anyone could still reach in and change that value from the outside, which is a nightmare for maintaining a predictable state in large apps.
Now, we have actual private class members. By prefixing a variable name with a #, you make it inaccessible from outside the class body. If you try to access it from an instance, JavaScript will throw a SyntaxError. It's not just a suggestion anymore; it's a hard wall.
class Wallet {
#balance = 0; // Private field
constructor(initialDeposit) {
this.#balance = initialDeposit;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
get balance() {
// We can provide a read-only getter, but they can't set it directly
return `Your balance is $${this.#balance}`;
}
}
const myWallet = new Wallet(100);
myWallet.deposit(50);
console.log(myWallet.balance); // "Your balance is $150"
// console.log(myWallet.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
// myWallet.#balance = 1000000; // SyntaxError
I'll be honest: you might be tempted to make everything public for "easier debugging." Don't do that. The more you can hide the inner workings of your class, the less you have to worry about some other part of your codebase accidentally breaking your logic by mutating a variable they weren't supposed to touch.
📋 Practical Task
Exercise: Building a Secure BankAccount Manager with Private Balances
Your goal is to create a BankAccount class that ensures financial integrity using the concepts from this lesson.
- Create a private field
#balanceinitialized to 0. - Create a static field
totalBankAssetsto track the sum of all money across all accounts. - Implement a
constructorthat accepts an initial deposit, updates the#balance, and adds that amount to thetotalBankAssets. - Implement a
withdraw(amount)method. It should deduct from the#balanceand thetotalBankAssets, but only if the user has enough funds. If they don't, it should return "Insufficient funds". - Implement a static method
getBankAudit()that returns the currenttotalBankAssets. - Ensure that the
#balancecannot be modified or accessed directly from outside the class.
Test your code by: Creating three different accounts with different deposits, performing a few withdrawals, and then calling the static audit method to verify the total is correct.
There are no comments for now.