In short
JavaScript classes are blueprints for creating many similar objects. Learn class syntax, constructors, getters, static and private fields, and inheritance. What is a class in JavaScript? A class is a blueprint, like a cookie cutter.
JavaScript classes are blueprints for creating many similar objects. Learn class syntax, constructors, getters, static and private fields, and inheritance.
What is a class in JavaScript?
A class is a blueprint, like a cookie cutter. You design the shape once, and then you can make as many cookies (objects) as you want. Each object made from a class is called an instance.
This style of organizing code around objects that hold data and the actions on that data is called object-oriented programming (OOP).
How to write a class with a constructor and methods
The constructor is a special method that runs when you create an instance with new. Inside it, this means "the new object being built". Methods are functions that every instance can use.
class BankAccount {
constructor(owner, balance = 0) {
this.owner = owner; // save data on the new object
this.balance = balance;
}
deposit(amount) {
this.balance += amount;
return `${this.owner} now has $${this.balance}`;
}
}
const maya = new BankAccount("Maya", 100);
const leo = new BankAccount("Leo");
console.log(maya.deposit(50)); // Output: Maya now has $150
console.log(leo.deposit(20)); // Output: Leo now has $20
maya and leo are two separate objects with their own data, but they share the same deposit method. Class names start with a capital letter by convention.
Getters, setters, static members and private fields
Classes have a few extra tools:
- A getter (
get) looks like a property but runs a function when you read it. - A setter (
set) runs a function when you assign a value, so you can check it first. - A static member belongs to the class itself, not to instances. It is good for helpers and counters.
- A private field starts with
#. It can only be used inside the class. Code outside cannot read or change it.
class Product {
static count = 0; // shared by the class
#price; // private field
constructor(name, price) {
this.name = name;
this.price = price; // goes through the setter below
Product.count++;
}
get price() {
return this.#price;
}
set price(value) {
if (value < 0) throw new Error("Price cannot be negative");
this.#price = value;
}
get label() {
return `${this.name} - $${this.#price.toFixed(2)}`;
}
static compare(a, b) {
return a.price - b.price;
}
}
const mug = new Product("Mug", 8);
const lamp = new Product("Lamp", 25);
console.log(mug.label); // Output: Mug - $8.00
mug.price = 10;
console.log(mug.price); // Output: 10
console.log(Product.count); // Output: 2
console.log([lamp, mug].sort(Product.compare).map((p) => p.name)); // Output: ["Mug", "Lamp"]
try {
mug.price = -5;
} catch (error) {
console.log(error.message); // Output: Price cannot be negative
}
// console.log(mug.#price); // SyntaxError: private field
Notice that label and price are used without (). That is what makes getters feel like normal properties. The #price field protects the data: the only way to change it is through the setter, which checks the value.
Note: Private
#fields are real privacy, enforced by the language. An older habit was to start names with_(like_price), but that is only a hint; anyone can still change it.
How does inheritance work with extends and super?
Inheritance lets one class build on another. The new class (the child) gets all the methods of the old class (the parent) and can add or change some. Use extends to inherit and super to call the parent.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // run the Animal constructor first
this.breed = breed;
}
speak() { // override the parent method
return `${super.speak()} Woof!`;
}
}
const rex = new Dog("Rex", "Beagle");
console.log(rex.speak()); // Output: Rex makes a sound. Woof!
console.log(rex.breed); // Output: Beagle
console.log(rex instanceof Dog); // Output: true
console.log(rex instanceof Animal); // Output: true
console.log(rex instanceof Array); // Output: false
- In a child constructor, you must call
super(...)before usingthis. super.speak()calls the parent's version of the method, so you can extend it instead of rewriting it.instanceofchecks if an object was made from a class or any of its parents.
Common mistake: Forgetting
new. Writingconst a = BankAccount("Maya")throws "Class constructor cannot be invoked without 'new'".
When should you use classes vs plain objects and functions?
Classes are not always the best choice. Here is a simple guide:
| Use this | When |
|---|---|
Plain object { } | You need one thing, or just data (settings, JSON from an API) |
| Functions | You transform data in and out with no saved state |
| Class | You need many objects with the same shape and behavior, or private state |
Good class examples are a Timer, a ShoppingCart, game characters or UI components. Keep inheritance shallow: one or two levels is usually enough. Deep chains like Animal > Mammal > Dog > SmallDog > Puppy get hard to change.
Practice exercise
Create a class Todo with title and a private #done field set to false. Add a method toggle(), a getter status that returns "Done" or "Open", and a static method fromJSON(obj) that creates a Todo from a plain object. Then create a PriorityTodo class that extends Todo and adds a priority property.
Hint: static fromJSON(obj) { return new Todo(obj.title); }.
Key takeaways
- A class is a blueprint;
newcreates an instance and runs theconstructor. - Getters and setters look like properties but run code.
staticmembers belong to the class;#privatefields are hidden from outside code.extendsinherits from a parent class, andsupercalls the parent.- Use classes for many similar objects with behavior; use plain objects and functions for simple data.