Constructor Functions in JavaScript: Complete Guide
Constructor functions are the original way to create object factories in JavaScript. Learn how new works, what happens step by step, and how constructor functions relate to classes.
A constructor function is a regular JavaScript function designed to be called with the new keyword. Its job is to create and initialize objects. Before ES6 classes existed, constructor functions were the standard way to build reusable object factories.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function () {
return `Hi, I am ${this.name} and I am ${this.age} years old.`;
};
const alice = new Person("Alice", 30);
console.log(alice.greet()); // "Hi, I am Alice and I am 30 years old."Person looks like an ordinary function, but calling it with new changes everything. Inside the function, this refers to a brand new object, and properties assigned to this become own properties of that instance. Methods added to Person.prototype are shared across every instance instead of being copied.
What new Does, Step by Step
When you write new Person("Alice", 30), JavaScript performs four steps behind the scenes: create an empty object, link its prototype to Person.prototype, run the function with this set to that object, and return the result.
Each step maps onto plain JavaScript you could write yourself, which is a useful way to demystify what new actually does.
// 1. const newObj = {};
// 2. Object.setPrototypeOf(newObj, Person.prototype);
// 3. Person.call(newObj, "Alice", 30);
// 4. const alice = newObj; (unless Person returns an object)You can confirm these steps happened by checking the prototype relationship directly on an instance, which shows the link was set up correctly during construction.
function Person(name) {
this.name = name;
}
const p = new Person("Alice");
console.log(Object.getPrototypeOf(p) === Person.prototype); // trueThe instance's prototype chain runs from p to Person.prototype to Object.prototype and finally to null. Methods placed on Person.prototype are found by walking that chain.
Adding Methods to the Prototype
Methods should go on Constructor.prototype, not inside the constructor body. If you define a method inside the constructor body instead, every single instance gets its own separate copy of that function, which wastes memory.
function WrongPerson(name) {
this.name = name;
this.greet = function () { return `Hi, I am ${this.name}`; };
}
const w1 = new WrongPerson("Alice");
const w2 = new WrongPerson("Bob");
console.log(w1.greet === w2.greet); // false, different function objectsMoving the method to the prototype fixes that: every instance then shares one function instead of storing its own copy.
function RightPerson(name) {
this.name = name;
}
RightPerson.prototype.greet = function () {
return `Hi, I am ${this.name}`;
};
const r1 = new RightPerson("Alice");
const r2 = new RightPerson("Bob");
console.log(r1.greet === r2.greet); // true, same function objectPlacing methods on the prototype is more memory-efficient and lets you change a method after instances already exist; every instance sees the update because lookup happens through the chain each time.
The prototype and constructor Properties
Every function, except arrow functions, automatically gets a .prototype property when it is created. That property starts as a plain object with one own property, constructor, which points back to the function itself.
function Dog(name) {
this.name = name;
}
console.log(Dog.prototype.constructor === Dog); // true
const rex = new Dog("Rex");
console.log(rex.constructor === Dog); // trueReading rex.constructor walks up the prototype chain and finds Dog.prototype.constructor, which points back to Dog, so you can use it to check what created an object. Be careful relying on this for critical logic, though: constructor is a writable property and can be overwritten, so it is not a guaranteed identity check.
Guarding Against Missing new
If someone calls a constructor function without new, this inside the function becomes undefined in strict mode or the global object in non-strict mode, so properties end up set on the wrong target entirely.
function Person(name) {
this.name = name;
}
const p = Person("Alice"); // forgot new
console.log(p); // undefinedYou can guard against this with new.target, a special value that is only defined when the function is actually called with new.
function Person(name) {
if (!new.target) {
throw new Error("Person must be called with new");
}
this.name = name;
}Classes enforce this automatically. Calling a class without new throws a TypeError on its own, which is one reason classes are safer than raw constructor functions for new code.
Constructor Functions vs Classes
Classes are syntax sugar over constructor functions; every class is a constructor function under the hood with a cleaner syntax on top.
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.drive = function () {
return `${this.make} ${this.model} is driving.`;
};The class version below produces an equivalent object shape, with prototype wiring handled automatically by the class keyword instead of a manual assignment line.
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
drive() {
return `${this.make} ${this.model} is driving.`;
}
}| Feature | Constructor Function | Class |
|---|---|---|
| Requires new? | Not enforced, manual guard needed | Enforced, throws TypeError |
| Methods | Manually added to .prototype | Written in the class body |
| Hoisting | Hoisted like regular functions | Not hoisted, temporal dead zone like let |
| Private fields | Not supported | Supported with # syntax |
| extends | Manual prototype chain setup | Built-in extends keyword |
Classes are the modern standard for new code because they are safer and more readable and have built-in support for private fields and static members. Understanding constructor functions still matters for reading older JavaScript and libraries that use them.
A Real Example: Building a Simple Model
Here is a constructor function that models a bank account with deposit and withdraw behavior and a running transaction history.
function BankAccount(owner, initialBalance) {
this.owner = owner;
this.balance = initialBalance;
this.transactions = [];
}
BankAccount.prototype.deposit = function (amount) {
this.balance += amount;
this.transactions.push({ type: "deposit", amount });
return this.balance;
};The withdraw method follows the same shape, adding a balance check before it changes any state so the account can never go negative.
BankAccount.prototype.withdraw = function (amount) {
if (amount > this.balance) return "Insufficient funds";
this.balance -= amount;
this.transactions.push({ type: "withdraw", amount });
return this.balance;
};
const account = new BankAccount("Alice", 1000);
account.deposit(500);
account.withdraw(200);
console.log(account.balance); // 1300Both methods live on BankAccount.prototype, so every account instance shares them while still keeping its own separate balance and transaction history. This same pattern powers a large share of pre-class JavaScript libraries and is the foundation that classes are built on. For the modern syntax, see JavaScript classes. For how this connects to inheritance, see the prototype chain.
Rune AI
Key Insights
- A constructor function is any function called with the new keyword.
- new creates a new object, sets its prototype to the function's .prototype, runs the function with this bound to that object, and returns it.
- Add shared methods to Constructor.prototype, not inside the constructor body.
- Every function (except arrow functions) has a .prototype property used only when the function is called with new.
- Classes are syntax sugar over constructor functions and prototypes.
Frequently Asked Questions
Can I use an arrow function as a constructor?
What happens if I forget new when calling a constructor?
Are constructor functions still used, or should I always use classes?
Conclusion
A constructor function is a regular function called with new. Inside it, this refers to a fresh object whose prototype is set to the function's .prototype property. Methods added to the prototype are shared across all instances. While classes are now the preferred syntax, understanding constructor functions gives you a complete mental model of how object creation works at the language level.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.