Skip to Content
Course content

72: Static and Private Class Members

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 #balance initialized to 0.
  • Create a static field totalBankAssets to track the sum of all money across all accounts.
  • Implement a constructor that accepts an initial deposit, updates the #balance, and adds that amount to the totalBankAssets.
  • Implement a withdraw(amount) method. It should deduct from the #balance and the totalBankAssets, 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 current totalBankAssets.
  • Ensure that the #balance cannot 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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.