Advanced

Classes and OOP

Model domains with ES6 classes, inheritance, and encapsulation.

What you will see

3.14159 42

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Model a domain with ES6 classes: define a class, inherit from a parent, and hide data behind static and private members. Each instance shares the methods but holds its own state.

1

Define a class with a constructor that stores state and a method that returns a templated value. Construct an instance and call the method on it.

class Dog { constructor(name) { this.name = name; } speak() { return `${this.name} makes a sound`; } } const rex = new Dog("Rex"); console.log(rex.speak());

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Rex makes a sound
The constructor initializes the instance. Methods live on the shared prototype, so this inside a method is always the specific instance.
2

Subclass a parent with extends and override a method. The child keeps the inherited field but supplies its own behavior for the shared method name.

class Animal { constructor(name) { this.name = name; } } class Cat extends Animal { speak() { return `${this.name} meows`; } } const cat = new Cat("Milo"); console.log(cat.speak()); console.log(cat.name);

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Milo meows Milo
`extends` inherits methods and `super()` calls the parent constructor. Overriding changes behavior while keeping the same method name.
3

Add a class-level static field and a truly private field with #. Read the static from the class and the private through a method; the private field is invisible outside.

class MathUtils { static PI = 3.14159; #secret = 0; constructor() { this.#secret = 42; } getSecret() { return this.#secret; } } console.log(MathUtils.PI); console.log(new MathUtils().getSecret());

Before you run it, say out loud what it will print. Then open the answer.

What it prints
3.14159 42
`static` members belong to the class, not instances. `#private` fields are inaccessible outside the class, unlike the `_` convention which is only advisory.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A class defines a blueprint; instances hold state and call shared methods that mutate it.
1 Define class with fields and methods
↓
2 new -> constructor sets instance state
↓
3 Call method -> receives this, reads/mutates state
↓
4 super -> subclass extends or overrides behavior
Why this works
When you have a choice

Prototypes vs classes

ES6 classes are syntactic sugar over prototype-based inheritance:

class A {}
console.log(Object.getPrototypeOf(rex) === Dog.prototype);
// true - instances share the prototype chain

Every instance's prototype points to the class's prototype object, which holds shared methods. Inheritance chains prototypes: a subclass instance's prototype has the parent's prototype in its chain. Classes just make this readable.

Encapsulation trade-offs

Choose the right encapsulation level:

class Bank {
  #balance = 0;               // private
  get balance() { return this#balance; }   // read-only via getter
  deposit(amount) { this#balance += amount; }
}

#private gives real privacy but is verbose. The _ prefix is conventional only, nothing stops access. Use getters/setters to expose a controlled API while keeping state hidden.

Examples

Defining a class

A simple class

class Dog {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} makes a sound`;
  }
}
const rex = new Dog("Rex");
console.log(rex.speak());   // Rex makes a sound

constructor initializes the instance. Methods live on the prototype, so all instances share them. this inside a method is the instance.

Inheritance

Subclassing

class Animal {
  constructor(name) { this.name = name; }
  sound() { return "..."; }
}
class Cat extends Animal {
  sound() { return `${this.name} meows`; }
}
const cat = new Cat("Milo");
console.log(cat.sound());   // Milo meows
console.log(cat.name);      // Milo (inherited)

extends inherits methods; super() calls the parent constructor. Overriding a method changes behavior while keeping the same interface.

Static and private fields

Class-level and private data

class MathUtils {
  static PI = 3.14159;
  #secret = 0;              // truly private
  constructor() { this.#secret = 42; }
  getSecret() { return this.#secret; }
}
console.log(MathUtils.PI);   // 3.14159 (static)
console.log(new MathUtils().getSecret());   // 42
// console.log(new MathUtils().#secret);    // SyntaxError

static members belong to the class, not instances. #private fields are inaccessible outside the class, unlike the _ convention which is only advisory.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Destructuring and Spread
Previous Lesson
Destructuring and Spread
Next Lesson
Modules and ES Modules
Modules and ES Modules →