Constructor Functions and the ’new’ Keyword in JavaScript
Introduction
Constructor functions are a fundamental pattern in JavaScript for creating multiple objects with the same structure and behavior. Before ES6 classes were introduced, constructor functions were the primary way to create object instances. Understanding constructor functions is essential for working with JavaScript’s prototype-based inheritance system.
In this article, you’ll learn how to create and use constructor functions, understand what the new keyword does, and explore best practices for this important pattern.
What is a Constructor Function?
A constructor function is a regular function that is designed to be called with the new keyword to create new objects. By convention, constructor function names start with a capital letter.
// Constructor function
function Person(name, age) {
this.name = name;
this.age = age;
}
// Creating instances with the 'new' keyword
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);
console.log(person1); // Person { name: 'Alice', age: 30 }
console.log(person2); // Person { name: 'Bob', age: 25 }
Understanding the ’new’ Keyword
When you call a function with the new keyword, JavaScript performs four steps:
- Creates a new empty object
- Sets the object’s prototype to the constructor’s prototype
- Calls the constructor function with
thisbound to the new object - Returns the new object (unless the constructor explicitly returns an object)
// What happens when you use 'new'
function Car(make, model) {
// Step 1: new object created (implicitly)
// Step 2: this = {} with Car.prototype as its prototype
this.make = make;
this.model = model;
// Step 4: return this (implicitly)
}
const myCar = new Car('Toyota', 'Camry');
console.log(myCar); // Car { make: 'Toyota', model: 'Camry' }
console.log(myCar instanceof Car); // true
Visualizing the ’new’ Keyword
// Manual simulation of what 'new' does
function Person(name) {
this.name = name;
}
// Without 'new' (wrong way)
const person1 = Person('Alice');
console.log(person1); // undefined
console.log(this.name); // 'Alice' (added to global object - bad!)
// With 'new' (correct way)
const person2 = new Person('Bob');
console.log(person2); // Person { name: 'Bob' }
console.log(person2.name); // 'Bob'
Constructor Functions with Methods
You can add methods to constructor functions using the prototype property.
// Constructor function
function Animal(name, species) {
this.name = name;
this.species = species;
}
// Adding methods to the prototype
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
Animal.prototype.getInfo = function() {
return `${this.name} is a ${this.species}`;
};
// Creating instances
const dog = new Animal('Rex', 'Dog');
const cat = new Animal('Whiskers', 'Cat');
console.log(dog.speak()); // Rex makes a sound
console.log(cat.getInfo()); // Whiskers is a Cat
Constructor Function Patterns
Pattern 1: Basic Constructor
function Book(title, author, year) {
this.title = title;
this.author = author;
this.year = year;
}
Book.prototype.getAge = function() {
return new Date().getFullYear() - this.year;
};
const book1 = new Book('1984', 'George Orwell', 1949);
console.log(book1.getAge()); // 76 (as of 2025)
Pattern 2: Constructor with Private Properties
function BankAccount(initialBalance) {
let balance = initialBalance; // Private variable
this.deposit = function(amount) {
balance += amount;
return balance;
};
this.withdraw = function(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return 'Insufficient funds';
};
this.getBalance = function() {
return balance;
};
}
const account = new BankAccount(1000);
console.log(account.deposit(500)); // 1500
console.log(account.withdraw(200)); // 1300
console.log(account.getBalance()); // 1300
console.log(account.balance); // undefined (private)
Pattern 3: Constructor with Default Values
function User(name, email, role = 'user') {
this.name = name;
this.email = email;
this.role = role;
this.createdAt = new Date();
}
const user1 = new User('Alice', '[email protected]');
const user2 = new User('Bob', '[email protected]', 'admin');
console.log(user1); // User { name: 'Alice', email: '[email protected]', role: 'user', ... }
console.log(user2); // User { name: 'Bob', email: '[email protected]', role: 'admin', ... }
Pattern 4: Constructor with Validation
function Email(address) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(address)) {
throw new Error('Invalid email address');
}
this.address = address;
}
try {
const email1 = new Email('[email protected]');
console.log(email1); // Email { address: '[email protected]' }
const email2 = new Email('invalid-email');
} catch (error) {
console.log(error.message); // Invalid email address
}
Constructor Functions vs Regular Functions
Key Differences
// Constructor function (called with 'new')
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
console.log(person); // Person { name: 'Alice' }
// Regular function (called without 'new')
function greet(name) {
return `Hello, ${name}`;
}
const greeting = greet('Bob');
console.log(greeting); // Hello, Bob
// What happens if you call a constructor without 'new'?
const person2 = Person('Charlie');
console.log(person2); // undefined
console.log(this.name); // 'Charlie' (added to global object - bad!)
Preventing Accidental Calls Without ’new'
function SafeConstructor(name) {
// Check if called with 'new'
if (!(this instanceof SafeConstructor)) {
return new SafeConstructor(name);
}
this.name = name;
}
// Works with 'new'
const obj1 = new SafeConstructor('Alice');
console.log(obj1); // SafeConstructor { name: 'Alice' }
// Also works without 'new' (automatically uses 'new')
const obj2 = SafeConstructor('Bob');
console.log(obj2); // SafeConstructor { name: 'Bob' }
Constructor Functions with Inheritance
Basic Inheritance Pattern
// Parent constructor
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
// Child constructor
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Add dog-specific methods
Dog.prototype.bark = function() {
return `${this.name} barks`;
};
const dog = new Dog('Rex', 'Labrador');
console.log(dog.speak()); // Rex makes a sound
console.log(dog.bark()); // Rex barks
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
Inheritance with Super-like Behavior
function Vehicle(make, model) {
this.make = make;
this.model = model;
}
Vehicle.prototype.getInfo = function() {
return `${this.make} ${this.model}`;
};
function Car(make, model, doors) {
Vehicle.call(this, make, model);
this.doors = doors;
}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.prototype.getInfo = function() {
const parentInfo = Vehicle.prototype.getInfo.call(this);
return `${parentInfo} with ${this.doors} doors`;
};
const car = new Car('Toyota', 'Camry', 4);
console.log(car.getInfo()); // Toyota Camry with 4 doors
Practical Real-World Examples
Example 1: User Management System
function User(username, email, password) {
this.username = username;
this.email = email;
this.password = password;
this.createdAt = new Date();
this.isActive = true;
}
User.prototype.deactivate = function() {
this.isActive = false;
};
User.prototype.updateEmail = function(newEmail) {
this.email = newEmail;
};
User.prototype.getProfile = function() {
return {
username: this.username,
email: this.email,
createdAt: this.createdAt,
isActive: this.isActive
};
};
const user = new User('john_doe', '[email protected]', 'secret123');
console.log(user.getProfile());
user.updateEmail('[email protected]');
console.log(user.getProfile());
Example 2: Shopping Cart
function ShoppingCart() {
this.items = [];
this.total = 0;
}
ShoppingCart.prototype.addItem = function(name, price, quantity = 1) {
this.items.push({ name, price, quantity });
this.total += price * quantity;
};
ShoppingCart.prototype.removeItem = function(name) {
const item = this.items.find(i => i.name === name);
if (item) {
this.total -= item.price * item.quantity;
this.items = this.items.filter(i => i.name !== name);
}
};
ShoppingCart.prototype.getTotal = function() {
return this.total.toFixed(2);
};
ShoppingCart.prototype.getItems = function() {
return this.items;
};
const cart = new ShoppingCart();
cart.addItem('Laptop', 999.99, 1);
cart.addItem('Mouse', 29.99, 2);
console.log(cart.getItems());
console.log('Total:', cart.getTotal());
Example 3: Task Manager
function Task(title, description, priority = 'medium') {
this.id = Date.now();
this.title = title;
this.description = description;
this.priority = priority;
this.completed = false;
this.createdAt = new Date();
}
Task.prototype.complete = function() {
this.completed = true;
};
Task.prototype.updatePriority = function(newPriority) {
this.priority = newPriority;
};
Task.prototype.getStatus = function() {
return this.completed ? 'Completed' : 'Pending';
};
function TaskList() {
this.tasks = [];
}
TaskList.prototype.addTask = function(title, description, priority) {
const task = new Task(title, description, priority);
this.tasks.push(task);
return task;
};
TaskList.prototype.completeTask = function(taskId) {
const task = this.tasks.find(t => t.id === taskId);
if (task) {
task.complete();
}
};
TaskList.prototype.getTasks = function(filter = 'all') {
if (filter === 'completed') {
return this.tasks.filter(t => t.completed);
}
if (filter === 'pending') {
return this.tasks.filter(t => !t.completed);
}
return this.tasks;
};
const taskList = new TaskList();
taskList.addTask('Learn JavaScript', 'Master constructor functions', 'high');
taskList.addTask('Build a project', 'Create a real-world application', 'medium');
console.log(taskList.getTasks());
Common Mistakes to Avoid
Mistake 1: Forgetting the ’new’ Keyword
// โ Wrong - Forgot 'new'
function Person(name) {
this.name = name;
}
const person = Person('Alice');
console.log(person); // undefined
console.log(this.name); // 'Alice' (pollutes global scope)
// โ
Correct
const person = new Person('Alice');
console.log(person); // Person { name: 'Alice' }
Mistake 2: Not Setting Up Prototype Correctly
// โ Wrong - Methods not shared
function Animal(name) {
this.name = name;
this.speak = function() { // Created for each instance
return `${this.name} speaks`;
};
}
// โ
Correct - Methods shared via prototype
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} speaks`;
};
Mistake 3: Incorrect Inheritance Setup
// โ Wrong - Breaks prototype chain
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Animal.prototype; // Wrong!
// โ
Correct - Maintains prototype chain
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Mistake 4: Modifying Constructor Prototype After Creating Instances
// โ Problematic - Changes affect all instances
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
const person2 = new Person('Bob');
// Both can use greet, but it's confusing
console.log(person1.greet()); // Works
console.log(person2.greet()); // Works
// โ
Better - Define all methods before creating instances
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
const person1 = new Person('Alice');
const person2 = new Person('Bob');
Constructor Functions vs ES6 Classes
Modern JavaScript provides ES6 classes, which are syntactic sugar over constructor functions.
// Constructor function (traditional)
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
// ES6 class (modern)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
// Both work the same way
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);
Summary
Constructor functions are a fundamental pattern in JavaScript:
- Constructor functions are regular functions called with the
newkeyword - The
newkeyword creates a new object and sets up the prototype chain - Methods should be defined on the prototype for memory efficiency
- Constructor functions can be used for inheritance with
Object.create() - Always use the
newkeyword when calling constructor functions - Modern ES6 classes provide a cleaner syntax for the same pattern
- Prevent accidental calls without
newusinginstanceofchecks
Related Resources
- MDN: Constructor Functions
- MDN: The ’new’ Operator
- MDN: Prototypes and Inheritance
- MDN: Object.create()
- MDN: Function.prototype.call()
Next Steps
Continue your learning journey:
- Prototypes and Prototype Chain
- ES6 Classes: Syntax and Features
- Inheritance: Extends and Super
- Object Composition Patterns
Comments