Classes and prototypes
class, private fields, getters, extends and what's under the hood.
Updated
What a class is
A class is a template for creating objects of the same kind. It describes once what data (properties) and behavior (methods) they have; then you create any number of instances with new.
class Account {
constructor(owner) { // runs on new — initializes the object
this.owner = owner
this.balance = 0
}
deposit(amount) { // a method — behavior shared by every account
this.balance += amount
}
}
const a = new Account('Ana') // an instance
const b = new Account('John') // another instance, with its own data
a.deposit(100)An analogy: the class is the cookie cutter, the instances are the cookies.
The parts of a class — kinds of members
| Member | Syntax | Belongs to | When |
|---|---|---|---|
| public field | count = 0 |
each instance | data visible from outside |
| private field | #balance = 0 |
each instance, inaccessible from outside | internal, protected data |
| method | deposit() {} |
shared by all instances | behavior |
| getter / setter | get total() {} / set total(v) {} |
the instance | a computed value read like a property |
| static | static from(json) {} |
the class, not the instance | factories, constants, helpers |
class Account {
#balance = 0 // private — acc.#balance is a SyntaxError
static currency = 'USD' // Account.currency
constructor(owner) { this.owner = owner }
deposit(amount) {
if (amount <= 0) throw new Error('Invalid amount')
this.#balance += amount
return this // allows chaining: acc.deposit(1).deposit(2)
}
get balance() { return this.#balance } // acc.balance — no parentheses
static fromJSON({ owner }) { return new Account(owner) }
}Inheritance
extends creates a class that takes over everything from another one and adds or changes something:
class Savings extends Account {
constructor(owner, rate) {
super(owner) // calls the parent's constructor — required before this
this.rate = rate
}
addInterest() { this.deposit(this.balance * this.rate) }
}Prototypes — what's under the hood
Classes are "syntactic sugar" over prototypes. Every object has a hidden link to another object (its prototype). When you ask for a property that doesn't exist on the object, JS looks for it in the prototype, then in the prototype's prototype — the prototype chain.
a.deposit // not on a, found on Account.prototype
Object.getPrototypeOf(a) === Account.prototype // true
a instanceof Account // true — Account.prototype is in the chainThat's why methods are shared (a single deposit in memory for every account), and [].map works on any array (it lives on Array.prototype).
Classes vs functions + objects
| Classes | Functions + plain objects | |
|---|---|---|
| Protected internal state | #private |
a closure |
| Inheritance | extends |
composition (you combine functions) |
this |
yes, with its traps | not needed |
| In modern React | only error boundaries | everything: components, hooks |
When to use classes in the frontend
- Yes: custom errors (
class HttpError extends Error), an API client / SDK, data structures (a queue, a cache), integrating with class-based libraries. - No: React components (they're functions today), simple data (an object literal is enough).
- Prefer composition (small pieces combined) over deep inheritance hierarchies —
A extends B extends C extends Dbecomes hard to change.
Summary
- A class = a template;
newcreates instances;constructorinitializes. #privatefields, getters,static,extends+super.- Under the hood: prototypes. In React, almost always functions.