Skip to main content

Constructors

A constructor is a special method that runs once, automatically when you create a new instance with new. Its only job is to initialize the object — set up properties and initial state.

Syntax

constructor() { /* … */ }
constructor(arg1, arg2, /* …, */ argN) { /* … */ }

Restrictions

  • A class can have only one constructor method — no overloading
  • It cannot be a getter, setter, async, or generator
  • Computed property ["constructor"]() does NOT become the real constructor — it's just a regular method
  • Private name #constructor is not allowed

Constructor Function (pre-ES6)

Any regular function can act as a constructor when called with new:

function User(name, age) {
this.name = name;
this.age = age;
}

const user = new User("Ajay", 25);
console.log(user.name); // "Ajay"
console.log(user instanceof User); // true

What new Does Behind the Scenes

  1. Creates a new empty object {}
  2. Sets its [[Prototype]] to Constructor.prototype
  3. Binds this to the new object
  4. Runs the constructor body
  5. Returns this (unless a non-primitive is explicitly returned)
// roughly equivalent to:
function User(name) {
// const this = Object.create(User.prototype); ← implicit
this.name = name;
// return this; ← implicit
}

Class Constructor (ES6+)

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

introduce() {
console.log(`Hello, my name is ${this.name}`);
}
}

const otto = new Person("Otto");
otto.introduce(); // "Hello, my name is Otto"

A class must be called with new — calling without throws:

Person("Otto"); // TypeError: Class constructor Person cannot be invoked without 'new'

Default Parameters Work Normally

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

new Person().name; // "Anonymous"

Execution Order with new

When new is called on a class, this is the exact sequence:

StepWhat happens
1Constructor body before super() runs (derived class only — this is NOT available yet)
2super() is evaluated → parent class goes through the same process
3Current class's fields are initialized
4Constructor body after super() runs (or entire body for base class)

This means class fields are initialized between the super() call and the rest of your constructor code.

Default Constructor

If you don't write a constructor, JavaScript provides one:

Base class — empty:

class Animal {}
// equivalent to:
class Animal {
constructor() {}
}

Derived class — forwards all arguments to parent:

class Dog extends Animal {}
// equivalent to:
class Dog extends Animal {
constructor(...args) {
super(...args);
}
}

Return Value Override

Base class — returning a non-primitive replaces this, primitives are ignored:

class ParentClass {
constructor() {
return 1; // ignored — not an object
}
}

console.log(new ParentClass()); // ParentClass {}
function Weird() {
this.a = 1;
return { a: 99 }; // overrides this
}

console.log(new Weird().a); // 99

Derived class — must return an object or undefined, anything else throws:

class ChildClass extends ParentClass {
constructor() {
super();
return 1; // TypeError: Derived constructors may only return object or undefined
}
}

What CAN'T Be a Constructor

Not every function has the internal [[Construct]] method:

TypeConstructor?Reason
Regular functionYesHas [[Construct]]
classYesDesigned for it
Arrow function () => {}NoNo [[Construct]], no this, no prototype
async functionNoCan't return an object synchronously
Shorthand method { foo() {} }NoNo [[Construct]]
Generator function*NoReturns iterator, not an instance
const arrow = () => {};
new arrow(); // TypeError: arrow is not a constructor

const obj = { method() {} };
new obj.method(); // TypeError: obj.method is not a constructor

Constructor with Inheritance

super() in Derived Classes

A subclass constructor must call super() before accessing this:

class Polygon {
constructor(width, height) {
this.width = width;
this.height = height;
}
}

class Square extends Polygon {
constructor(length) {
super(length, length); // must come before `this`
this.name = "Square";
}

get area() {
return this.width * this.height;
}
}

const sq = new Square(5);
console.log(sq.area); // 25
console.log(sq.name); // "Square"

Private Fields Are Not Ready During super()

If the parent constructor calls a method that accesses a child's private field, it will fail because private fields aren't initialized until after super() returns:

class Base {
constructor() {
console.log(this.foo()); // TypeError!
}
}

class Child extends Base {
#value = 1;
foo() {
return this.#value; // #value not initialized yet when Base constructor runs
}
}

Constructor Function Inheritance (pre-ES6)

function Animal(name) {
this.name = name;
}

function Dog(name, breed) {
Animal.call(this, name); // borrow parent constructor
this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Real-World Pattern: Custom Error

class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
this.code = "42";
}

printMessage() {
return `Validation failed (details: ${this.message}, code: ${this.code})`;
}
}

try {
throw new ValidationError("Not a valid phone number");
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.name); // "ValidationError"
console.log(error.printMessage()); // "Validation failed (details: Not a valid phone number, code: 42)"
}
}

Protecting Against Missing new

Constructor functions (not classes) silently pollute global scope without new:

function User(name) {
this.name = name;
}

const u = User("Ajay"); // forgot new
console.log(u); // undefined
console.log(window.name); // "Ajay" — leaked to global!

Guard with new.target:

function User(name) {
if (!new.target) {
return new User(name);
}
this.name = name;
}

const u = User("Ajay"); // works correctly
console.log(u.name); // "Ajay"

Classes don't need this guard — they throw automatically without new.

Computed ["constructor"] Gotcha

A computed property named "constructor" is not the real constructor:

class Foo {
["constructor"]() {
console.log("called");
this.a = 1;
}
}

const foo = new Foo(); // nothing logged — real constructor (default) ran
console.log(foo); // Foo {}

foo.constructor(); // "called" — it's just a regular method
console.log(foo); // Foo { a: 1 }

References

Key Takeaways

  • A constructor initializes an object at creation time — runs once per new call.
  • new creates the object, links the prototype, binds this, and runs the constructor.
  • Only one constructor per class — no overloading.
  • Arrow functions, async functions, and shorthand methods cannot be constructors.
  • Classes enforce new — constructor functions don't (use new.target to guard).
  • In subclasses, super() must be called before this.
  • Class fields initialize after super() returns, not before.
  • If a constructor returns a non-primitive, it replaces the constructed object.
  • Derived class constructors can only return an object or undefined.