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
constructormethod — 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
#constructoris 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
- Creates a new empty object
{} - Sets its
[[Prototype]]toConstructor.prototype - Binds
thisto the new object - Runs the constructor body
- 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:
| Step | What happens |
|---|---|
| 1 | Constructor body before super() runs (derived class only — this is NOT available yet) |
| 2 | super() is evaluated → parent class goes through the same process |
| 3 | Current class's fields are initialized |
| 4 | Constructor 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:
| Type | Constructor? | Reason |
|---|---|---|
Regular function | Yes | Has [[Construct]] |
class | Yes | Designed for it |
Arrow function () => {} | No | No [[Construct]], no this, no prototype |
async function | No | Can't return an object synchronously |
Shorthand method { foo() {} } | No | No [[Construct]] |
Generator function* | No | Returns 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
newcall. newcreates the object, links the prototype, bindsthis, 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 (usenew.targetto guard). - In subclasses,
super()must be called beforethis. - 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.