class is clean syntax over prototypes — with real extras: public and #private fields, static members, static blocks, accessors, and strict mode by default.
Anatomy of a class
class BankAccount {
static bankName = "JS Bank"; // static field (on the class)
static #count = 0; // private static field
owner; // public instance field
#balance = 0; // private instance field
constructor(owner, deposit = 0) {
this.owner = owner;
this.#balance = deposit;
BankAccount.#count++;
}
deposit(amount) { // method on BankAccount.prototype
if (amount <= 0) throw new RangeError("amount must be positive");
this.#balance += amount;
return this; // enables chaining
}
get balance() { return this.#balance; } // accessor
#audit() { return `audited ${this.owner}`; } // private method
report() { return this.#audit(); }
static count() { return BankAccount.#count; }
}
const acc = new BankAccount("Asha", 100);
acc.deposit(50).deposit(25);
console.log(acc.balance, acc.report(), acc);
console.log(BankAccount.bankName, BankAccount.count(), typeof BankAccount);175 audited Asha BankAccount { owner: 'Asha' }
JS Bank 1 functionPrivate really means private
class Secret {
#code = 1234;
static isSecret(obj) { return #code in obj; } // brand check (ES2022)
}
const s = new Secret();
console.log(s.code, Object.keys(s), JSON.stringify(s));
console.log(Secret.isSecret(s), Secret.isSecret({ code: 1 }));undefined [] {}
true falseAccessing s.#code outside the class body is a SyntaxError — the program does not even parse. Before #, the convention was a leading underscore (this._code), which is only a hint.
Class fields with arrow functions
class Button {
label = "Save";
handleArrow = () => `clicked ${this.label}`; // per-instance, this is bound
handleMethod() { return `clicked ${this?.label}`; }
}
const b = new Button();
const { handleArrow, handleMethod } = b; // detach both
console.log(handleArrow(), "|", handleMethod.call(undefined));
console.log(Object.hasOwn(b, "handleArrow"), Object.hasOwn(b, "handleMethod"));clicked Save | clicked undefined
true falseStatic blocks and getters
class Config {
static defaults;
static {
// runs once when the class is defined
Config.defaults = Object.freeze({ retries: 3, timeout: 1000 });
}
static get retries() { return Config.defaults.retries; }
}
console.log(Config.retries, Config.defaults);3 { retries: 3, timeout: 1000 }Classes are special functions
| Behaviour | Detail |
|---|---|
Must be called with new | BankAccount() → TypeError: Class constructor … cannot be invoked without 'new' |
| Hoisted into the TDZ | Using a class before its declaration throws ReferenceError |
| Strict mode | The whole class body is strict |
| Methods are non-enumerable | They do not show up in for…in over instances |
| Fields run per instance | Initialised before the constructor body (after super() in subclasses) |
class Foo {}
try {
Foo();
} catch (e) {
console.log(e.message);
}Class constructor Foo cannot be invoked without 'new'Are JavaScript classes real classes?
How do you make a property private?