Skip to content

JavaScript Classes

This lesson explains JavaScript Classes with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.

ES6 Classes Overview

ES6 classes provide a cleaner syntax for creating objects, constructors, methods, inheritance, and reusable object-oriented code in JavaScript. Classes are built on top of JavaScript prototypes, but they make object creation easier to read and organize.

Classes are commonly used for models, services, utilities, custom components, API clients, state managers, design system components, reusable business logic, and object-oriented application architecture.

Feature Description Example Syntax
Class Blueprint for creating objects. class User { ... }
Constructor Runs when a new object is created. constructor(name) { ... }
Method Function defined inside a class. login() { ... }
Inheritance Create a class from another class. class Admin extends User
super Calls parent constructor or methods. super(name)
Static Method Method called on class, not instance. static create() { ... }

Basic Class Example

class User {
  constructor(name, email) {
    this.name =
      name;

    this.email =
      email;
  }

  showProfile() {
    return this.name + " - " + this.email;
  }
}

const user =
  new User(
    "Alice",
    "alice@example.com"
  );

console.log(
  user.showProfile()
);

The class keyword defines a class. The constructor method initializes values when the object is created with new.

Constructor Method

class Product {
  constructor(name, price) {
    this.name =
      name;

    this.price =
      price;
  }
}

const product =
  new Product(
    "Laptop",
    1200
  );

console.log(product.name);
console.log(product.price);

A constructor is used to set initial properties for each object instance. A class can have only one constructor method.

Instance Methods

class Cart {
  constructor() {
    this.items =
      [];
  }

  addItem(item) {
    this.items.push(item);
  }

  getTotalItems() {
    return this.items.length;
  }
}

const cart =
  new Cart();

cart.addItem("Keyboard");
cart.addItem("Mouse");

console.log(
  cart.getTotalItems()
);

Instance methods are called on objects created from the class. They can access instance properties using this.

this Keyword in Classes

class Counter {
  constructor() {
    this.count =
      0;
  }

  increment() {
    this.count += 1;

    return this.count;
  }
}

const counter =
  new Counter();

console.log(
  counter.increment()
);

Inside class methods, this refers to the current object instance.

Class Expression

const User =
  class {
    constructor(name) {
      this.name =
        name;
    }

    greet() {
      return "Hello " + this.name;
    }
  };

const user =
  new User("John");

console.log(
  user.greet()
);

Classes can also be stored in variables as class expressions, although class declarations are more common.

Getters and Setters

class Employee {
  constructor(firstName, lastName) {
    this.firstName =
      firstName;

    this.lastName =
      lastName;
  }

  get fullName() {
    return this.firstName + " " + this.lastName;
  }

  set fullName(value) {
    const parts =
      value.split(" ");

    this.firstName =
      parts[0];

    this.lastName =
      parts[1];
  }
}

const employee =
  new Employee(
    "Alice",
    "Johnson"
  );

console.log(employee.fullName);

employee.fullName =
  "Bob Smith";

console.log(employee.fullName);

Getters read computed values like properties. Setters update values using property assignment syntax.

Static Methods

class MathHelper {
  static add(a, b) {
    return a + b;
  }

  static multiply(a, b) {
    return a * b;
  }
}

console.log(
  MathHelper.add(10, 20)
);

console.log(
  MathHelper.multiply(5, 4)
);

Static methods belong to the class itself, not to object instances. They are useful for utility methods, factories, validation helpers, and parsing logic.

Static Properties

class AppConfig {
  static appName =
    "Admin Dashboard";

  static version =
    "1.0.0";

  static getInfo() {
    return this.appName + " " + this.version;
  }
}

console.log(
  AppConfig.getInfo()
);

Static properties store values on the class itself. They are useful for constants, configuration, shared labels, and metadata.

Class Inheritance with extends

class User {
  constructor(name) {
    this.name =
      name;
  }

  login() {
    return this.name + " logged in.";
  }
}

class Admin extends User {
  deleteUser(userName) {
    return userName + " deleted.";
  }
}

const admin =
  new Admin("Sarah");

console.log(
  admin.login()
);

console.log(
  admin.deleteUser("John")
);

The extends keyword creates a child class that inherits properties and methods from a parent class.

Using super in Constructor

class User {
  constructor(name, email) {
    this.name =
      name;

    this.email =
      email;
  }
}

class Admin extends User {
  constructor(name, email, role) {
    super(name, email);

    this.role =
      role;
  }
}

const admin =
  new Admin(
    "Alice",
    "alice@example.com",
    "Manager"
  );

console.log(admin.name);
console.log(admin.role);

In a child class constructor, super() must be called before using this.

Calling Parent Methods with super

class Notification {
  send(message) {
    return "Sending: " + message;
  }
}

class EmailNotification extends Notification {
  send(message) {
    const baseMessage =
      super.send(message);

    return baseMessage + " by email.";
  }
}

const email =
  new EmailNotification();

console.log(
  email.send("Welcome")
);

super.methodName() calls a method from the parent class.

Method Overriding

class Animal {
  speak() {
    return "Animal sound";
  }
}

class Dog extends Animal {
  speak() {
    return "Dog barks";
  }
}

const dog =
  new Dog();

console.log(
  dog.speak()
);

A child class can define a method with the same name as the parent method. This is called method overriding.

Private Class Fields

class BankAccount {
  #balance =
    0;

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }

  getBalance() {
    return this.#balance;
  }
}

const account =
  new BankAccount();

account.deposit(100);

console.log(
  account.getBalance()
);

Private fields use the # prefix and cannot be accessed directly from outside the class.

Private Methods

class Validator {
  #isEmail(value) {
    return value.includes("@");
  }

  validateEmail(value) {
    return this.#isEmail(value);
  }
}

const validator =
  new Validator();

console.log(
  validator.validateEmail("test@example.com")
);

Private methods are useful for internal class logic that should not be exposed to consumers.

Public Class Fields

class TodoStore {
  todos =
    [];

  addTodo(text) {
    this.todos.push(
      {
        text,
        completed: false
      }
    );
  }
}

const store =
  new TodoStore();

store.addTodo("Learn ES6 classes");

console.log(store.todos);

Public fields can be declared directly inside a class without writing them inside the constructor.

Arrow Function Class Field

class ButtonController {
  count =
    0;

  handleClick =
    () => {
      this.count += 1;

      console.log(this.count);
    };
}

const controller =
  new ButtonController();

document
  .querySelector("#button")
  .addEventListener(
    "click",
    controller.handleClick
  );

Arrow function fields keep this bound to the class instance. This is useful for event handlers in UI code.

Real-World Example: API Client Class

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl =
      baseUrl;
  }

  async get(path) {
    const response =
      await fetch(this.baseUrl + path);

    if (!response.ok) {
      throw new Error(
        "Request failed"
      );
    }

    return response.json();
  }

  async post(path, data) {
    const response =
      await fetch(
        this.baseUrl + path,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          body: JSON.stringify(data)
        }
      );

    return response.json();
  }
}

const api =
  new ApiClient("/api");

api.get("/users")
  .then((users) => {
    console.log(users);
  });

Classes are useful for grouping related API methods and shared configuration.

Real-World Example: Model Class

class Product {
  constructor(data) {
    this.id =
      data.id;

    this.name =
      data.name;

    this.price =
      data.price;
  }

  getFormattedPrice() {
    return "$" + this.price.toFixed(2);
  }

  isExpensive() {
    return this.price > 1000;
  }
}

const product =
  new Product(
    {
      id: 1,
      name: "Laptop",
      price: 1299
    }
  );

console.log(
  product.getFormattedPrice()
);

Model classes can group data and behavior together in a readable way.

Real-World Example: UI Component Class

class Modal {
  constructor(selector) {
    this.element =
      document.querySelector(selector);
  }

  open() {
    this.element.hidden =
      false;
  }

  close() {
    this.element.hidden =
      true;
  }
}

const modal =
  new Modal("#loginModal");

document
  .querySelector("#openModal")
  .addEventListener("click", () => {
    modal.open();
  });

UI classes can manage DOM elements, state, and behavior for reusable components.

Classes vs Constructor Functions

// Constructor function

function User(name) {
  this.name =
    name;
}

User.prototype.greet =
  function() {
    return "Hello " + this.name;
  }

// ES6 class

class ModernUser {
  constructor(name) {
    this.name =
      name;
  }

  greet() {
    return "Hello " + this.name;
  }
}

Classes are mostly cleaner syntax over prototype-based object creation.

Classes Use Prototypes

class User {
  greet() {
    return "Hello";
  }
}

const user =
  new User();

console.log(
  User.prototype.greet === user.__proto__.greet
);

Class methods are stored on the prototype, not copied into every instance. This helps memory usage.

instanceof with Classes

class User {}

const user =
  new User();

console.log(
  user instanceof User
);

// true

The instanceof operator checks whether an object was created from a class or its prototype chain.

Class vs Object Literal

Feature Class Object Literal
Best For Creating many similar objects. Single object or configuration.
Constructor Yes. No special constructor.
Inheritance Supported with extends. Manual composition preferred.
Methods Shared through prototype. Stored directly on object.
Common Use Models, services, components. Settings, constants, simple data.

Common ES6 Class Use Cases

  • Creating reusable model objects.
  • Building API client services.
  • Managing UI components.
  • Creating validation utilities.
  • Building data stores.
  • Writing custom elements and Web Components.
  • Organizing business rules.
  • Creating reusable framework-independent modules.

Common ES6 Class Mistakes

  • Forgetting to use new when creating an instance.
  • Using this before super() in a child constructor.
  • Using classes for simple data that could be plain objects.
  • Creating deep inheritance chains that are hard to maintain.
  • Forgetting that class methods are not automatically bound.
  • Confusing static methods with instance methods.
  • Expecting private fields to be accessible outside the class.
  • Putting too much unrelated logic into one large class.

ES6 Class Best Practices

  • Use classes when you need many similar objects with shared behavior.
  • Use object literals for simple configuration or one-off objects.
  • Keep class responsibilities focused.
  • Prefer composition over deep inheritance.
  • Use private fields for internal state when needed.
  • Use static methods for utility behavior related to the class.
  • Use clear constructor parameters or object options.
  • Write small methods with clear names.

Key Takeaways

  • ES6 classes provide cleaner syntax for prototype-based objects.
  • The constructor method initializes object instances.
  • Instance methods are shared through the prototype.
  • extends creates inheritance between classes.
  • super() calls the parent constructor or method.
  • Static methods belong to the class, not the instance.
  • Private fields use the # prefix.
  • Use classes only when they improve organization and reuse.

Pro Tip

In interviews, remember this simple explanation: ES6 classes are cleaner syntax over JavaScript's prototype system. Use them for reusable objects, services, models, and components, but avoid deep inheritance when composition would be simpler.