In Grade 9, you used closures to keep a bank balance private. Classes have their own built-in way to do this: private fields, whose names start with a #. They can only be used by code inside the class:
class BankAccount {
#balance = 0;
constructor(owner) {
this.owner = owner;
}
deposit(amount) {
if (amount <= 0) throw new Error("Deposit must be positive");
this.#balance += amount;
return this.#balance;
}
withdraw(amount) {
if (amount > this.#balance) {
console.log("❌ Insufficient funds");
return this.#balance;
}
this.#balance -= amount;
return this.#balance;
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount("Lucas");
account.deposit(500);
account.withdraw(1000);
account.withdraw(120);
console.log(`${account.owner} has $${account.balance}`);
try {
account.balance = 1000000; // nice try!
} catch (error) {
console.log("🚫", error.message);
}
console.log(`${account.owner} still has $${account.balance}`);
It prints
❌ Insufficient funds Lucas has $380 🚫 Cannot set property balance of #<BankAccount> which has only a getter Lucas still has $380
#balance is completely hidden from the outside world. We only gave it a getter, without a setter, so account.balance = 1000000 can’t change it: JavaScript throws an error instead, which we catch with try/catch. And if you try to touch the private field directly from outside the class...
console.log(account.#balance);
It stops with an error
Uncaught SyntaxError: Private field '#balance' must be declared in an enclosing class
...JavaScript won't even run the code. This idea of hiding an object's internal details and only exposing safe methods is called encapsulation, and it's one of the big ideas of OOP. (Notice also #balance = 0; at the top of the class: you can declare fields with starting values directly in the class body, outside the constructor.)