Classes
Learn how to use JavaScript classes to create reusable, structured objects with inheritance.
Classes
In the Objects section we created objects directly using object literals. That works fine for one-off objects — but what if you need to create many objects with the same structure and behavior? A hundred users, each with a name, email, and methods?
Classes are a blueprint for creating objects. Define the structure once — create as many instances as you need.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return `Hello, I am ${this.name}.`;
}
}
const ali = new User("Ali", "ali@example.com");
const sara = new User("Sara", "sara@example.com");
console.log(ali.greet()); // Hello, I am Ali.
console.log(sara.greet()); // Hello, I am Sara.One blueprint. Two instances. Each with their own data but sharing the same methods.
Class Syntax
class ClassName {
constructor(parameters) {
// initialize instance properties
this.property = value;
}
methodName() {
// method body
}
}class— the keyword to define a classconstructor— a special method that runs when you create a new instance withnewthis— refers to the instance being created- Methods — defined directly in the class body — no
functionkeyword needed
Creating Instances
Use the new keyword to create an instance from a class. Each call to new creates a fresh independent object.
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
this.inStock = true;
}
getDetails() {
return `${this.name} — Rs. ${this.price}`;
}
applyDiscount(percent) {
this.price = this.price * (1 - percent / 100);
return this;
}
}
const laptop = new Product("Laptop", 120000);
const mouse = new Product("Mouse", 1500);
console.log(laptop.getDetails()); // Laptop — Rs. 120000
console.log(mouse.getDetails()); // Mouse — Rs. 1500
laptop.applyDiscount(10);
console.log(laptop.getDetails()); // Laptop — Rs. 108000
console.log(mouse.price); // 1500 — mouse is unchangedEach instance has its own name, price, and inStock — completely independent.
Class Fields
Modern JavaScript lets you declare properties directly in the class body — outside the constructor. These are called class fields.
class User {
// Class fields — default values
role = "user";
isVerified = false;
loginCount = 0;
constructor(name, email) {
this.name = name;
this.email = email;
}
login() {
this.loginCount++;
console.log(`${this.name} logged in. Total logins: ${this.loginCount}`);
}
}
const ali = new User("Ali", "ali@example.com");
console.log(ali.role); // user
console.log(ali.isVerified); // false
ali.login(); // Ali logged in. Total logins: 1Class fields are cleaner than setting defaults in the constructor — especially for values that do not depend on constructor arguments.
Private Fields
Private fields start with # and are only accessible inside the class. No code outside the class can read or modify them.
class BankAccount {
#balance = 0; // private — cannot be accessed from outside
#owner;
constructor(owner, initialBalance) {
this.#owner = owner;
this.#balance = initialBalance;
}
deposit(amount) {
if (amount <= 0) throw new Error("Deposit must be positive.");
this.#balance += amount;
console.log(`Deposited Rs. ${amount}. Balance: Rs. ${this.#balance}`);
return this;
}
withdraw(amount) {
if (amount > this.#balance) throw new Error("Insufficient funds.");
this.#balance -= amount;
console.log(`Withdrew Rs. ${amount}. Balance: Rs. ${this.#balance}`);
return this;
}
getBalance() {
return this.#balance; // controlled access through a method
}
getStatement() {
return `Account owner: ${this.#owner} | Balance: Rs. ${this.#balance}`;
}
}
const account = new BankAccount("Ali", 10000);
account.deposit(5000).withdraw(3000);
console.log(account.getBalance()); // 12000
console.log(account.getStatement()); // Account owner: Ali | Balance: Rs. 12000
console.log(account.#balance); // ❌ SyntaxError — private fieldPrivate fields enforce encapsulation — internal implementation details stay internal.
Static Methods and Fields
Static methods and fields belong to the class itself — not to any instance. You call them on the class, not on an object.
class MathHelper {
static PI = 3.14159;
static add(a, b) { return a + b; }
static multiply(a, b) { return a * b; }
static circleArea(radius) {
return MathHelper.PI * radius * radius;
}
}
console.log(MathHelper.PI); // 3.14159
console.log(MathHelper.add(5, 3)); // 8
console.log(MathHelper.circleArea(7)); // 153.93...
const math = new MathHelper();
console.log(math.add(5, 3)); // ❌ TypeError — add is not on instancesStatic methods are useful for utility functions that are related to a class but do not need instance data.
Factory methods — a common static pattern
class User {
constructor(name, email, role) {
this.name = name;
this.email = email;
this.role = role;
}
static createAdmin(name, email) {
return new User(name, email, "admin");
}
static createGuest() {
return new User("Guest", "guest@example.com", "guest");
}
}
const admin = User.createAdmin("Ali", "ali@example.com");
const guest = User.createGuest();
console.log(admin.role); // admin
console.log(guest.name); // GuestFactory methods are named constructors — they create instances in specific ways without exposing the constructor details.
Inheritance — extends
A class can extend another class — inheriting all its properties and methods, and adding or overriding its own.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
toString() {
return `Animal: ${this.name}`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // call parent constructor first
this.breed = breed;
}
speak() {
return `${this.name} barks!`; // override parent method
}
fetch(item) {
return `${this.name} fetches the ${item}!`;
}
}
class Cat extends Animal {
constructor(name, color) {
super(name);
this.color = color;
}
speak() {
return `${this.name} meows.`;
}
purr() {
return `${this.name} purrs...`;
}
}
const dog = new Dog("Rex", "German Shepherd");
const cat = new Cat("Whiskers", "orange");
console.log(dog.speak()); // Rex barks!
console.log(dog.fetch("ball")); // Rex fetches the ball!
console.log(cat.speak()); // Whiskers meows.
console.log(cat.purr()); // Whiskers purrs...
console.log(dog.toString()); // Animal: Rex — inherited from Animalsuper — calling the parent
super refers to the parent class. In the constructor it calls the parent constructor. In methods it calls the parent version of that method.
class Employee extends User {
constructor(name, email, department, salary) {
super(name, email); // must call super before using this
this.department = department;
this.salary = salary;
}
greet() {
const baseGreeting = super.greet(); // call parent greet
return `${baseGreeting} I work in ${this.department}.`;
}
}
const emp = new Employee("Ali", "ali@example.com", "Engineering", 150000);
console.log(emp.greet());
// Hello, I am Ali. I work in Engineering.In a child class constructor, you must call super() before accessing this. Forgetting this throws a ReferenceError.
instanceof — Checking the Chain
instanceof checks if an object is an instance of a class — or any class in its inheritance chain.
const dog = new Dog("Rex", "German Shepherd");
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true — Dog extends Animal
console.log(dog instanceof Cat); // falseGetters and Setters
Getters and setters let you define properties that run code when accessed or assigned.
class Temperature {
#celsius;
constructor(celsius) {
this.#celsius = celsius;
}
get fahrenheit() {
return this.#celsius * 9/5 + 32;
}
set fahrenheit(value) {
this.#celsius = (value - 32) * 5/9;
}
get celsius() {
return this.#celsius;
}
set celsius(value) {
if (value < -273.15) {
throw new RangeError("Temperature below absolute zero.");
}
this.#celsius = value;
}
}
const temp = new Temperature(100);
console.log(temp.celsius); // 100
console.log(temp.fahrenheit); // 212
temp.fahrenheit = 32;
console.log(temp.celsius); // 0
temp.celsius = -300; // ❌ RangeErrorGetters and setters look like property access — not method calls — which makes the API feel natural.
A Real Example — Content Management System
class Content {
#id;
static #nextId = 1;
constructor(title, body, author) {
this.#id = Content.#nextId++;
this.title = title;
this.body = body;
this.author = author;
this.createdAt = new Date();
this.published = false;
}
get id() { return this.#id; }
get excerpt() {
return this.body.slice(0, 100) + (this.body.length > 100 ? "..." : "");
}
publish() {
this.published = true;
this.publishedAt = new Date();
console.log(`"${this.title}" published.`);
return this;
}
toString() {
return `[${this.#id}] ${this.title} by ${this.author}`;
}
}
class Article extends Content {
constructor(title, body, author, tags = []) {
super(title, body, author);
this.tags = tags;
this.readTime = Math.ceil(body.split(" ").length / 200); // minutes
}
addTag(tag) {
if (!this.tags.includes(tag)) {
this.tags.push(tag);
}
return this;
}
toString() {
return `${super.toString()} [${this.tags.join(", ")}] — ${this.readTime} min read`;
}
}
class Video extends Content {
constructor(title, body, author, duration) {
super(title, body, author);
this.duration = duration; // seconds
}
get formattedDuration() {
const mins = Math.floor(this.duration / 60);
const secs = this.duration % 60;
return `${mins}:${secs.toString().padStart(2, "0")}`;
}
toString() {
return `${super.toString()} [Video: ${this.formattedDuration}]`;
}
}
const article = new Article(
"JavaScript Classes",
"Classes in JavaScript provide a clean syntax for creating objects and handling inheritance...",
"Ali Hassan",
["javascript", "es6"]
);
const video = new Video(
"Async JavaScript Explained",
"In this video we cover callbacks, promises, and async/await...",
"Sara Khan",
754
);
article.addTag("tutorial").publish();
video.publish();
console.log(article.toString());
// [1] JavaScript Classes by Ali Hassan [javascript, es6, tutorial] — 1 min read
console.log(video.toString());
// [2] Async JavaScript Explained by Sara Khan [Video: 12:34]
console.log(article.excerpt);
// Classes in JavaScript provide a clean syntax for creating objects and handling inheritance...
console.log(article instanceof Content); // true
console.log(video instanceof Content); // trueClasses Are Syntactic Sugar
An important thing to know — JavaScript classes are not a fundamentally new feature. They are syntactic sugar over JavaScript's existing prototype-based inheritance system.
// Class syntax
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} speaks.`; }
}
// What JavaScript actually does under the hood
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { return `${this.name} speaks.`; };Both do exactly the same thing. Classes are just a cleaner, more readable way to write what JavaScript was already doing with prototypes. We cover prototypes in depth in the Advanced section.
Summary
- A class is a blueprint for creating objects with shared structure and behavior
- The constructor runs when you create an instance with
new— use it to set initial properties - Class fields — declare properties with default values outside the constructor
- Private fields
#name— only accessible inside the class — enforce encapsulation - Static methods and fields — belong to the class itself, not instances — useful for utilities and factory methods
- Inheritance with
extends— child class gets all parent properties and methods super()in the constructor calls the parent constructor — must come beforethissuper.method()calls the parent version of an overridden method- Getters and setters —
getandsetkeywords — run code on property access or assignment - Classes are syntactic sugar over JavaScript's prototype system — covered in the Advanced section