← All concepts
Object-Oriented Programming

ES6 Classes

A class is syntax on top of a constructor function and its prototype, not a new kind of object.

Definition

The `class` keyword, introduced in ES6, is syntactic sugar over the constructor function and prototype pattern JavaScript already had. Writing `class Dog { constructor(name) { this.name = name; } bark() { return this.name + " barks"; } }` produces the same shape under the hood as a `function Dog(name)` with `this.name = name`, plus a `bark` method assigned to `Dog.prototype`. Every method written inside the class body, other than the constructor, is placed on the prototype automatically, so all instances share one copy instead of getting their own. Calling `new Dog("Rex")` creates a new plain object, sets its internal prototype to `Dog.prototype`, runs the constructor with `this` bound to that new object, and returns it. `class Puppy extends Dog` builds `Puppy.prototype` with `Dog.prototype` as its own prototype, chaining the two, and `super(name)` inside `Puppy`'s constructor calls `Dog`'s constructor first. Classes add conveniences the older pattern does not have for free, like a body that throws if called without `new`, and methods that are non-enumerable by default, but the underlying mechanism is the same prototype chain.

Examples

class Dog {
  constructor(name) {
    this.name = name;
  }
  bark() {
    return this.name + " barks";
  }
}
const rex = new Dog("Rex");
rex.bark(); // "Rex barks"
typeof Dog.prototype.bark; // "function"

bark is defined in the class body but ends up on Dog.prototype, exactly where a hand-written prototype method would live.

class Animal {
  constructor(name) { this.name = name; }
  speak() { return this.name + " makes a sound"; }
}
class Cat extends Animal {
  speak() { return this.name + " meows"; }
}
new Cat("Milo").speak(); // "Milo meows"

extends links Cat.prototype to Animal.prototype, and Cat's own speak on its prototype shadows Animal's version for Cat instances.

class Counter {
  static instances = 0;
  constructor() {
    Counter.instances++;
  }
}
new Counter();
new Counter();
Counter.instances; // 2

static fields and methods live on the class itself, Counter, rather than on Counter.prototype or on individual instances.

Common mistakes

  • Forgetting to call super() in a subclass constructor before using this; a derived class constructor must call super() first.
  • Calling a class without new, which throws, unlike an old-style constructor function that would just run with the wrong this.
  • Assuming methods are copied onto each instance; they live once on the prototype, so overriding one instance's method does not touch the class.

Key takeaways

  • class is sugar over a constructor function plus methods placed on its prototype.
  • new creates the object, links it to Class.prototype, and runs the constructor with this bound to the new object.
  • extends chains one prototype to another, and super calls the parent constructor or method.
  • Static members live on the class itself, not on the prototype or on instances.
Check your understanding

class Animal { speak() { return "..."; } } const a = new Animal(); Where does the speak method actually live?

Where you see this

  • Modeling domain objects like ShoppingCart or BankAccount with clear constructors and shared methods.
  • Building inheritance hierarchies, like a base Shape class with Square and Rect subclasses overriding area().
  • Reading unfamiliar codebases that mix class syntax with older constructor-function code, knowing they compile to the same thing.
  • Using static methods for utilities that belong to the class itself rather than to any one instance.

Practice this

Further reading

Previous
Prototypes and Inheritance
Next
Getters and Setters