Academic Block

Classes (ES6) Get Certified

ES6 introduced the class keyword, which provides a cleaner and more familiar syntax for creating objects and working with inheritance. Under the hood it still uses the same prototype system you learned about in the previous lesson.

Basic Class Syntax

A class is defined with the class keyword. The constructor method runs automatically when you create a new instance with new.

What this code does: Defines a simple class with a constructor and an instance method, then creates an instance and calls the method.

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

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

const michael = new Person("Michael");
console.log(michael.greet());
console.log(typeof Person);   // "function"

Class Fields

You can declare fields directly in the class body. They are initialized on every new instance.

What this code does: Uses a class field to give every instance a default value without putting it inside the constructor.

class Counter {
    count = 0;

    increment() {
        this.count++;
        return this.count;
    }
}

const c = new Counter();
console.log(c.increment());
console.log(c.increment());
console.log(c.count);

Getters and Setters

Getters and setters let you control how a property is read or written while still using normal property syntax.

What this code does: Defines a getter for the computed area and a setter for diameter that updates the underlying radius.

class Circle {
    constructor(radius) {
        this.radius = radius;
    }

    get area() {
        return Math.PI * this.radius * this.radius;
    }

    set diameter(value) {
        this.radius = value / 2;
    }
}

const circle = new Circle(5);
console.log(circle.area.toFixed(2));

circle.diameter = 20;
console.log(circle.radius);

Static Methods

A static method belongs to the class itself, not to individual instances. It is useful for utility functions related to the class.

What this code does: Creates a static utility method that belongs to the class and can be called without creating an instance.

class MathHelper {
    static square(n) {
        return n * n;
    }
}

console.log(MathHelper.square(4));

Static Fields

Static fields belong to the class rather than to any single instance. They are commonly used to track information shared across all instances.

What this code does: Uses a static field to count how many times the class has been instantiated.

class Counter {
    static instanceCount = 0;

    constructor() {
        Counter.instanceCount++;
    }
}

new Counter();
new Counter();
new Counter();

console.log(Counter.instanceCount);

Private Fields

Prefixing a field name with # makes it private — it can only be accessed from inside the class itself.

What this code does: Declares a private field #balance that can only be read or modified from inside the class methods.

class BankAccount {
    #balance = 0;

    deposit(amount) {
        this.#balance += amount;
        return this.#balance;
    }

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

const account = new BankAccount();
account.deposit(100);
console.log(account.balance);

// account.#balance would cause a SyntaxError outside the class

Private Methods

Methods can also be made private with the same # prefix. They are useful for internal logic that should not be part of the class’s public interface.

What this code does: Uses a private method #validate that is only callable from other methods of the same class.

class Account {
    #validate(amount) {
        return amount > 0;
    }

    deposit(amount) {
        if (!this.#validate(amount)) {
            throw new Error("Invalid amount");
        }
        return "Deposited " + amount;
    }
}

const account = new Account();
console.log(account.deposit(50));

Class Expressions

Just like functions, classes can also be written as expressions and assigned to a variable.

What this code does: Shows that a class can be written as an expression and assigned to a variable.

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

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

const g = new Greeter("Michael");
console.log(g.greet());

Inheritance with extends and super

A class can inherit from another class using extends. Inside a subclass, super refers to the parent class.

What this code does: Demonstrates inheritance with extends and method overriding that calls the parent version via super.

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

    speak() {
        return this.name + " makes a sound.";
    }
}

class Dog extends Animal {
    speak() {
        return super.speak() + " Specifically, a bark.";
    }
}

const rex = new Dog("Rex");
console.log(rex.speak());

Instance vs. Static vs. Private Members

Member Type Accessed On Visible Outside the Class?
Instance method / field An instance (e.g. michael.greet()) Yes
Static method / field The class itself (e.g. MathHelper.square()) Yes
Private field / method (#name) Only from inside the class No
Getter / setter An instance, accessed like a property Yes

Common Mistakes with Classes

  • Forgetting the new keyword when creating an instance.
  • Calling a static method on an instance instead of the class, or vice versa.
  • Trying to access a private field or method (#name) from outside the class.
  • Extracting a method from an instance and losing its this binding.
  • Forgetting super() before using this in a subclass constructor.

What this code does: Extracts a method from an instance and shows that calling it without the original object causes a this-related error.

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

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

const michael = new Person("Michael");
const greetFn = michael.greet;

try {
    console.log(greetFn());
} catch (error) {
    console.log("Error:", error.message);
}

Best Practices for Classes

  • Keep the constructor focused on setting up initial state.
  • Use private fields (#name) to protect internal state.
  • Prefer getters for computed, read-only values.
  • Use static methods for behavior related to the class as a whole.
  • Keep inheritance chains shallow; prefer composition when appropriate.

Quick Summary

  • The constructor method runs automatically when a class is instantiated with new.
  • Instance methods are shared through the class’s prototype; class fields are declared directly in the class body.
  • Getters and setters let computed properties be accessed and assigned like normal properties.
  • static members belong to the class itself, not to individual instances.
  • Private fields and methods (#name) are only accessible from inside the class.
  • Classes can also be written as expressions, not just declarations.
  • extends and super provide inheritance between classes.

Try It Yourself

Modify the class below. Try adding a new private field, a static counter, or a getter that computes something from existing fields.

What this code does: A complete practice class combining a static counter, a private price field, a getter for formatted output, and a static helper method.

class Product {
    static totalProducts = 0;
    #price;

    constructor(name, price) {
        this.name = name;
        this.#price = price;
        Product.totalProducts++;
    }

    get formattedPrice() {
        return "$" + this.#price.toFixed(2);
    }

    static getTotalProducts() {
        return Product.totalProducts;
    }
}

const laptop = new Product("Laptop", 999.99);
const mouse = new Product("Mouse", 25);

console.log(laptop.name, laptop.formattedPrice);
console.log(mouse.name, mouse.formattedPrice);
console.log("Total products:", Product.getTotalProducts());
Tip: Use instance members for per-object data and behavior, static members for anything belonging to the class as a whole, and private fields whenever internal state should not be touched from outside the class.

Understanding ES6 classes gives you the modern, readable syntax for the same prototype-based system covered in the Prototypes & Inheritance lesson, and forms the foundation for object-oriented patterns in JavaScript.

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close