The constructor method serves as a specialized function within an ES6 class body, executing automatically whenever a new instance is generated. If this method is absent, the compiler implicitly treats it as a no-op.
When utilizing the new operator, the interpreter follows a specific lifecycle:
- It allocates a new blank object in heap memory.
- It links the object’s internal prototype (
[[Prototype]]) to the class’s static prototype property. - It binds the
thiskeyword inside the method to refer to the new object. - It executes the statements within the
constructorto populate properties. - It returns the object, unless an explicit object reference is returned; otherwise, the implicitly created object defaults to the result.
Example showing basic setup:
class Employee {
constructor(role) {
console.log('role assigned:', role);
}
}
class Equipment {
constructor(condition) {
this.status = condition || 'neutral';
}
}
let staff = new Employee('Dev');
let gear = new Equipment('working');
console.log(gear.status); // working
Parameters provided during instantiation are mapped to constructor arguments. Omitting parentheses is permissible if no arguments are required, though including them is harmless.
class Account {
constructor(owner) {
this.name = owner || 'Unknown';
}
}
let a1 = new Account; // Arguments undefined
let a2 = new Account(); // Arguments undefined
let a3 = new Account('Alex'); // Argument present
console.log(a1.name); // Unknown
console.log(a3.name); // Alex
A constructor implicitly returns this. If the function returns another distinct object, that object overrides the result, but it will lack the prototype linkage. Consequently, the instanceof operator will report false for the original class because the returned object does not inherit from the class prototype.
class Entity {
constructor(mode) {
this.primary = 'default';
if (mode) {
return { secondary: 'custom' };
}
}
}
let e1 = new Entity(false);
let e2 = new Entity(true);
console.log(e1 instanceof Entity); // true
console.log(e2 instanceof Entity); // false
Crucially, class constructors cannot be invoked without the new operator. Calling them as plain functions raises a TypeError. Unlike traditional function constructors, which default this to the global scope in sloppy mode, classes enforce strict binding requirements.
function Legacy() { this.data = true; }
class Modern {}
Legacy(); // Works, attaches to global/window
Modern(); // Throws TypeError
Invoking the constructor reference directly on an instance fails due to binding rules, but the reference can be used to spawn new instances successfully.
let p1 = new Employee('Manager');
p1.constructor(); // Invalid
let p2 = new p1.constructor('Lead'); // Valid
From the engine's perspective, class declarations are essentially functions. Their type check confirms this, and they expose a prototype object containing a constructor property pointing back to the class itself.
console.log(typeof Modern); // function
console.log(Modern === Modern.prototype.constructor); // true
The instanceof operator verifies the existence of the prototype chain. While the standard check works, comparing an instance against the class's constructor property yields different results depending on how the object was initialized.
let obj = new Modern();
console.log(obj.constructor === Modern); // true
console.log(obj instanceof Modern); // true
console.log(obj instanceof Modern.constructor); // false
Since classes are first-class values, they can be stored in arrays and passed as arguments, enabling flexible factory patterns.
const pool = [
class Service { constructor(id) { this.id_ = id; } }
];
function generate(cls, val) {
return new cls(val);
}
generate(pool[0], 99);
Additionally, classes support immediate execution expressions similar to IIFEs, allowing inline instance creation with out naming the class separately.
let res = new class Wrapper {
setup(value) { this.value = value; }
}(55);
console.log(res.value); // 55