JavaScriptClass Constructors

Class Constructors

The constructor is the special method that runs when you say new SomeClass(...). Its job is to take the arguments, initialise the new instance, and stop. A class can declare exactly one constructor — but with default parameters, destructuring, and new.target, that one slot is more flexible than it looks.

The basic constructor

JS
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

const p = new Point(3, 4);
console.log(p);   // Point { x: 3, y: 4 }

If you omit the constructor entirely, the engine inserts a default one — constructor() {} for a base class, and constructor(...args) { super(...args); } for a subclass.

Default parameter tricks

Default parameters trigger only on undefined, which is exactly the behaviour you want for optional constructor arguments. They also give a clean way to make a struct-shaped constructor.

JS
class User {
  constructor({
    name = "anonymous",
    age = 0,
    isAdmin = false,
  } = {}) {
    this.name = name;
    this.age = age;
    this.isAdmin = isAdmin;
  }
}

console.log(new User());                        // User { name: 'anonymous', age: 0, isAdmin: false }
console.log(new User({ name: "Ada", age: 36 }));// User { name: 'Ada', age: 36, isAdmin: false }

The trailing = {} means new User() is also valid — without it, destructuring undefined throws.

Class fields versus constructor assignment

Modern syntax lets you declare and initialise instance fields directly in the class body. The expressions run at the start of construction, just before the constructor body. Use whichever style fits — they are functionally equivalent for simple defaults.

JS
class Counter {
  count = 0;                  // field — set on every new instance
  history = [];

  constructor(start = 0) {
    this.count = start;       // overrides the field default
  }
}

console.log(new Counter().count);     // 0
console.log(new Counter(10).count);   // 10
Returning a value from a constructor

Normally a constructor returns the new instance automatically. If you explicitly return an object, that object is used instead — primitives are ignored.

JS
class Strange {
  constructor() {
    this.tag = "ignored";
    return { hijacked: true };   // overrides — what new produces
  }
}

console.log(new Strange());   // { hijacked: true }

class Normal {
  constructor() {
    return 42;     // primitive — ignored, regular instance returned
  }
}
console.log(new Normal());   // Normal {}
Rarely useful
Returning a different object from a constructor is a real feature, but it confuses readers. Reserve it for advanced patterns like singletons or proxies — and document it loudly.
new.target — was I called with new?

Inside a function or constructor, new.target refers to the function that was used with new, or is undefined if the function was called without new. Classes throw automatically when called without new, but you can use new.target to enforce or detect subclassing.

JS
class Base {
  constructor() {
    console.log("new.target is", new.target?.name);
    if (new.target === Base) {
      throw new Error("Base is abstract — subclass it");
    }
  }
}

class Child extends Base {}

new Child();          // new.target is Child
try {
  new Base();         // throws
} catch (e) {
  console.log("caught:", e.message);
}
Classes cannot be called without new

Unlike pre-class constructor functions, calling a class without new throws — there is no quiet fallback. That mistake is easy to catch.

JS
class Point { constructor(x, y) { this.x = x; this.y = y; } }

try {
  Point(1, 2);     // missing new
} catch (e) {
  console.log(e.message);
}
Class constructor Point cannot be invoked without 'new'
What runs before your constructor body
  • For a base class: instance fields are evaluated and assigned in declaration order, then your constructor body runs.

  • For a subclass: super(...) must be called before you read or assign this. The fields declared on the subclass run after super returns.

  • Throwing inside the constructor aborts construction — the half-built instance is unreachable.

Pick a style and stick to it
Either declare every instance field at the top of the class with defaults, or assign them all in the constructor. Mixing both styles for the same set of fields makes the lifecycle harder to read.
One sentence
The constructor is just a method named `constructor` that runs once per `new` call — defaults, destructuring, and `new.target` are normal JavaScript working inside it.