A constructor enables you to provide any custom initialization that must be done before any other methods can be called on an instantiated object.
class Person {
constructor(name) {
this.name = name;
}
introduce() {
console.log(`Hello, my name is ${this.name}`);
}
}
const otto = new Person("Otto");
otto.introduce();
If you don't provide your own constructor, then a default constructor will be supplied for you. If your class is a base class, the default constructor is empty:
If your class is a derived class, the default constructor calls the parent constructor, passing along any arguments that were provided:
constructor(...args) {
super(...args);
}
Note: The difference between an explicit constructor like the one above and the default constructor is that the latter doesn't actually invoke the array iterator through argument spreading.
That enables code like this to work:
class ValidationError extends Error {
printCustomerMessage() {
return `Validation failed :-( (details: ${this.message})`;
}
}
try {
throw new ValidationError("Not a valid phone number");
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.name);
console.log(error.printCustomerMessage());
} else {
console.log("Unknown error", error);
throw error;
}
}
The ValidationError
class doesn't need an explicit constructor, because it doesn't need to do any custom initialization. The default constructor then takes care of initializing the parent Error
from the argument it is given.
However, if you provide your own constructor, and your class derives from some parent class, then you must explicitly call the parent class constructor using super()
. For example:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
this.code = "42";
}
printCustomerMessage() {
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);
console.log(error.printCustomerMessage());
} else {
console.log("Unknown error", error);
throw error;
}
}
Using new
on a class goes through the following steps:
- (If it's a derived class) The
constructor
body before the super()
call is evaluated. This part should not access this
because it's not yet initialized. - (If it's a derived class) The
super()
call is evaluated, which initializes the parent class through the same process. - The current class's fields are initialized.
- The
constructor
body after the super()
call (or the entire body, if it's a base class) is evaluated.
Within the constructor
body, you can access the object being created through this
and access the class that is called with new
through new.target
. Note that methods (including getters and setters) and the prototype chain are already initialized on this
before the constructor
is executed, so you can even access methods of the subclass from the constructor of the superclass. However, if those methods use this
, the this
will not have been fully initialized yet. This means reading public fields of the derived class will result in undefined
, while reading private fields will result in a TypeError
.
new (class C extends class B {
constructor() {
console.log(this.foo());
}
} {
#a = 1;
foo() {
return this.#a;
}
})();
The constructor
method may have a return value. While the base class may return anything from its constructor, the derived class must return an object or undefined
, or a TypeError
will be thrown.
class ParentClass {
constructor() {
return 1;
}
}
console.log(new ParentClass());
class ChildClass extends ParentClass {
constructor() {
return 1;
}
}
console.log(new ChildClass());
If the parent class constructor returns an object, that object will be used as the this
value on which class fields of the derived class will be defined. This trick is called "return overriding", which allows a derived class's fields (including private ones) to be defined on unrelated objects.
The constructor
follows normal method syntax, so parameter default values, rest parameters, etc. can all be used.
class Person {
constructor(name = "Anonymous") {
this.name = name;
}
introduce() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person = new Person();
person.introduce();
The constructor must be a literal name. Computed properties cannot become constructors.
class Foo {
["constructor"]() {
console.log("called");
this.a = 1;
}
}
const foo = new Foo();
console.log(foo);
foo.constructor();
console.log(foo);
Async methods, generator methods, accessors, and class fields are forbidden from being called constructor
. Private names cannot be called #constructor
. Any member named constructor
must be a plain method.