JS Constructor Functions: A Complete Tutorial
Master JavaScript constructor functions — the original OOP pattern before classes. Learn how the new operator works, prototype-based method sharing, the constructor property, instanceof checks, and the relationship to modern class syntax.
Before ES6 classes arrived in 2015, constructor functions were the primary way to create objects with shared behavior in JavaScript. Even today, understanding constructor functions is essential because class syntax is built directly on top of them — classes are syntactic sugar, not a separate inheritance model. This tutorial covers constructor functions from first principles to their relationship with modern class syntax.
What a Constructor Function Is
A constructor function is a regular function invoked with the new keyword. By convention, constructor functions are named with a capital first letter (PascalCase) to distinguish them from regular functions:
// Constructor function — PascalCase convention
function Person(name, age) {
this.name = name;
this.age = age;
}
// Called with new
const alice = new Person("Alice", 30);
console.log(alice.name); // "Alice"
console.log(alice.age); // 30Without new, this does not refer to a new object — in non-strict mode it refers to window/global, in strict mode it is undefined.
How the new Operator Works
The new operator does four things automatically:
- Creates a new empty object:
{} - Sets its
[[Prototype]]toConstructor.prototype - Executes the constructor with
thisbound to the new object - Returns the new object (unless the constructor explicitly returns a different object)
// What new does under the hood:
function simulateNew(Constructor, ...args) {
// Step 1 + 2: Create object linked to prototype
const obj = Object.create(Constructor.prototype);
// Step 3: Call constructor with new object as this
const result = Constructor.apply(obj, args);
// Step 4: Return new object (or explicit object return)
return result instanceof Object ? result : obj;
}
function Animal(type) {
this.type = type;
}
const dog1 = new Animal("dog");
const dog2 = simulateNew(Animal, "dog");
console.log(dog1.type === dog2.type); // trueAdding Methods via the Prototype
Defining methods inside the constructor body creates a new function copy per instance. The correct pattern is adding methods to Constructor.prototype:
function Person(name, age) {
this.name = name;
this.age = age;
// WRONG: creates a new greet function for every Person instance
// this.greet = function() { return `Hi, I'm ${this.name}`; };
}
// CORRECT: shared via prototype — all instances use one function object
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}, ${this.age} years old.`;
};
Person.prototype.birthday = function() {
this.age += 1;
};
const alice = new Person("Alice", 30);
const bob = new Person("Bob", 25);
console.log(alice.greet()); // "Hi, I'm Alice, 30 years old."
console.log(bob.greet()); // "Hi, I'm Bob, 25 years old."
// Both instances share the exact same function object
console.log(alice.greet === bob.greet); // trueThis is the foundation of prototype-based method sharing — a core concept in JavaScript's prototype chain.
The constructor Property
Every Constructor.prototype object has a constructor property pointing back to the constructor function:
function Car(make) { this.make = make; }
console.log(Car.prototype.constructor === Car); // true
const myCar = new Car("Toyota");
console.log(myCar.constructor === Car); // true (inherited via prototype)
console.log(myCar.constructor.name); // "Car"When you replace Constructor.prototype entirely (rather than augmenting it), the constructor pointer is lost and must be restored:
function Vehicle(type) { this.type = type; }
// Replacing the entire prototype object — LOSES constructor
Vehicle.prototype = {
describe() { return `A ${this.type}`; },
// Missing constructor!
};
const v = new Vehicle("bus");
console.log(v.constructor === Vehicle); // false — broken!
console.log(v.constructor === Object); // true — inherits Object.prototype.constructor
// Fix: restore the constructor property
Vehicle.prototype = {
constructor: Vehicle, // ← Explicitly restore
describe() { return `A ${this.type}`; },
};instanceof and Prototype Linkage
instanceof checks whether a constructor's .prototype exists anywhere in the instance's prototype chain:
function Dog(name) { this.name = name; }
function Animal() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const rex = new Dog("Rex");
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true (via chain)
console.log(rex instanceof Object); // true (everything is)instanceof checks the prototype chain, not constructor identity — it can be fooled by manual prototype manipulation.
Prototype Inheritance With Constructor Functions
Before classes, inheritance was established by linking prototype chains:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound.`;
};
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor to initialize inherited properties
this.breed = breed;
}
// Link Dog.prototype to Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Restore constructor
// Override speak
Dog.prototype.speak = function() {
return `${this.name} barks.`;
};
const rex = new Dog("Rex", "Labrador");
console.log(rex.speak()); // "Rex barks."
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // trueThis is exactly what class / extends does under the hood. For more detail, see how prototypal inheritance works in JavaScript.
Constructor Functions vs Factory Functions
Factory functions are a no-new alternative:
// Factory function — no new, no this, returns an object literal
function createPerson(name, age) {
return {
name,
age,
greet() { return `Hi, I'm ${name}`; },
};
}
const alice = createPerson("Alice", 30);
console.log(alice.greet()); // "Hi, I'm Alice"| Aspect | Constructor Function | Factory Function |
|---|---|---|
| Invocation | new PersonConstructor() | createPerson() |
instanceof | Works (prototype linkage) | Broken (plain object) |
this context | Bound to new instance | Not needed |
| Prototype sharing | Via Constructor.prototype | Per-instance copy (memory cost) |
| Private state (closures) | Awkward with prototype pattern | Natural via closure |
Return Value Behavior
A constructor's return value is special:
function Example() {
this.x = 1;
return { x: 999 }; // ← Returning an object — this replaces the new object!
}
function Example2() {
this.x = 1;
return 42; // ← Returning a primitive — ignored, new object returned
}
const e1 = new Example();
console.log(e1.x); // 999 — override with returned object
const e2 = new Example2();
console.log(e2.x); // 1 — primitive return ignored, normal behaviorConstructor Functions vs ES6 Classes
ES6 classes compile down to essentially the same constructor-function pattern:
// Constructor function pattern
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return `(${this.x}, ${this.y})`;
};
// Equivalent ES6 class
class PointClass {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
// Both produce the same prototype structure
const p1 = new Point(1, 2);
const p2 = new PointClass(1, 2);
console.log(p1.toString()); // "(1, 2)"
console.log(p2.toString()); // "(1, 2)"Key difference: classes are not hoisted (use before declaration throws ReferenceError), and class bodies run in strict mode automatically. See JavaScript classes explained for the full class syntax breakdown.
Comparison Table: Constructor vs Class
| Feature | Constructor Function | ES6 Class |
|---|---|---|
| Syntax | function Foo() {} | class Foo {} |
| Hoisting | Hoisted (declaration) | Not hoisted (TDZ) |
| Strict mode | Optional | Automatic |
| Static methods | Foo.staticFn = fn | static staticFn() {} |
| Private fields | Workarounds only | #field syntax (ES2022) |
| Inheritance | Object.create + call | extends + super() |
| Readability | Lower | Higher |
Rune AI
Key Insights
- new does four things: Creates an empty object, sets its [[Prototype]] to Constructor.prototype, runs the function with this bound to the object, and returns the object
- Methods belong on the prototype: Defining methods in the constructor body duplicates them per instance; prototype methods are shared (one function object for all instances)
- Replacing prototype breaks constructor: If you overwrite Constructor.prototype entirely, restore the constructor property manually:
Constructor.prototype.constructor = Constructor - instanceof checks the chain: instanceof returns true if Constructor.prototype appears anywhere in the instance's prototype chain, not just the immediate prototype
- Classes are constructor functions: ES6 class syntax compiles to the same prototype structure — understanding constructor functions means understanding how classes actually work
Frequently Asked Questions
Can I call a constructor function without new?
Why are methods placed on the prototype instead of in the constructor body?
Is it still relevant to know constructor functions if I use classes?
What is the difference between Object.create and new Constructor?
Conclusion
Constructor functions are the original OOP mechanism in JavaScript. The new operator creates an object, sets its prototype, and runs the constructor body. Methods are shared via Constructor.prototype rather than per-instance copies. The constructor property links instances back to their constructor. instanceof traverses the prototype chain for type checks. ES6 classes are clean syntax sugar over this exact pattern, so mastering constructor functions gives you deep understanding of how JavaScript's object system really works.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.