DocsHub
ES6+

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 class
  • constructor — a special method that runs when you create a new instance with new
  • this — refers to the instance being created
  • Methods — defined directly in the class body — no function keyword 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 unchanged

Each 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: 1

Class 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 field

Private 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 instances

Static 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); // Guest

Factory 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.

Animalname, speak Dogbreed, fetch Catcolor, purr new Dog instance new Cat instance
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 Animal

super — 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);    // false

Getters 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; // ❌ RangeError

Getters 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);   // true

Classes 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 before this
  • super.method() calls the parent version of an overridden method
  • Getters and settersget and set keywords — run code on property access or assignment
  • Classes are syntactic sugar over JavaScript's prototype system — covered in the Advanced section

On this page