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
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.
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.
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); // 10Returning 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.
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 {}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.
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.
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
constructorbody runs.For a subclass:
super(...)must be called before you read or assignthis. The fields declared on the subclass run aftersuperreturns.Throwing inside the constructor aborts construction — the half-built instance is unreachable.