JavaScript Constructor Functions
Introduction
In JavaScript, a constructor function is a special type of function that serves as a blueprint for creating multiple similar objects. Instead of creating objects manually using object literals ({}
), constructor functions provide a template that can be used to instantiate objects with the same properties and methods.
Constructor functions are one of JavaScript's primary ways to implement object-oriented programming concepts, allowing you to create multiple instances of similar objects with shared functionality. They were the standard way to create object templates before ES6 classes were introduced (though they still work under the hood in modern JavaScript).
Creating Constructor Functions
A constructor function looks similar to a regular function, but by convention, we name constructor functions with a capital first letter to distinguish them from regular functions.
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return `Hello, my name is ${this.name} and I am ${this.age} years old`;
};
}
The key aspects of constructor functions:
- The function name starts with a capital letter (convention)
- Properties and methods are assigned to
this
- They are meant to be called with the
new
keyword
Using the new
Keyword
To create objects using a constructor function, you must use the new
keyword:
const john = new Person('John Doe', 30);
const jane = new Person('Jane Smith', 25);
console.log(john);
// Output: Person {name: 'John Doe', age: 30, greet: ƒ}
console.log(jane);
// Output: Person {name: 'Jane Smith', age: 25, greet: ƒ}
console.log(john.greet());
// Output: 'Hello, my name is John Doe and I am 30 years old'
What Happens When You Call a Constructor Function
When you call a constructor function with the new
keyword, the following steps occur:
- A new empty object is created
- The function's
this
is bound to that new object - The code inside the constructor function executes, adding properties to the new object
- The new object is returned (unless the constructor explicitly returns another object)
// This is what happens behind the scenes when you call "new Person()"
function constructorExample() {
// 1. Create an empty object and assign it to `this`
// const this = {};
// 2. Add properties to `this`
this.name = 'John';
this.age = 30;
// 3. Implicitly return `this`
// return this;
}
Constructor Function Prototype
One challenge with constructor functions is that methods (like greet
in our example) are recreated for each instance, which is inefficient. To solve this problem, we can use the prototype:
function Person(name, age) {
this.name = name;
this.age = age;
}
// Add methods to the prototype instead
Person.prototype.greet = function() {
return `Hello, my name is ${this.name} and I am ${this.age} years old`;
};
const john = new Person('John Doe', 30);
const jane = new Person('Jane Smith', 25);
console.log(john.greet()); // Still works!
// Output: 'Hello, my name is John Doe and I am 30 years old'
With this approach, the greet
method is created only once and shared across all Person instances, saving memory.
Checking Instance Type
You can check if an object is an instance of a particular constructor using the instanceof
operator:
console.log(john instanceof Person); // Output: true
console.log({} instanceof Person); // Output: false
Common Issues with Constructor Functions
Forgetting the new
Keyword
If you forget to use the new
keyword when calling a constructor function, this
will not refer to a new object but to the global object (or undefined
in strict mode), which can cause bugs:
const bob = Person('Bob Smith', 40); // Forgot 'new'
console.log(bob);
// Output: undefined (nothing was returned)
// In non-strict mode, properties were added to the global object!
console.log(window.name); // 'Bob Smith' (in a browser environment)
To prevent this issue, you can add a safeguard to your constructor:
function Person(name, age) {
if (!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
Practical Example: Creating a Library System
Let's create a more complex example of a library system using constructor functions:
function Book(title, author, isbn, pages) {
this.title = title;
this.author = author;
this.isbn = isbn;
this.pages = pages;
this.isCheckedOut = false;
}
Book.prototype.checkOut = function() {
if (this.isCheckedOut) {
return `${this.title} is already checked out.`;
}
this.isCheckedOut = true;
return `${this.title} has been checked out.`;
};
Book.prototype.returnBook = function() {
if (!this.isCheckedOut) {
return `${this.title} hasn't been checked out yet.`;
}
this.isCheckedOut = false;
return `${this.title} has been returned.`;
};
Book.prototype.getSummary = function() {
return `${this.title} by ${this.author}, ${this.pages} pages, ISBN: ${this.isbn}`;
};
function Library(name) {
this.name = name;
this.books = [];
}
Library.prototype.addBook = function(book) {
if (!(book instanceof Book)) {
throw new Error('Only Book objects can be added to the library');
}
this.books.push(book);
return `${book.title} has been added to ${this.name}.`;
};
Library.prototype.findBookByTitle = function(title) {
return this.books.find(book => book.title === title) || null;
};
// Usage:
const myLibrary = new Library('Community Library');
const book1 = new Book('JavaScript: The Good Parts', 'Douglas Crockford', '0596517742', 176);
const book2 = new Book('Eloquent JavaScript', 'Marijn Haverbeke', '1593275846', 472);
console.log(myLibrary.addBook(book1));
// Output: 'JavaScript: The Good Parts has been added to Community Library.'
console.log(myLibrary.addBook(book2));
// Output: 'Eloquent JavaScript has been added to Community Library.'
const foundBook = myLibrary.findBookByTitle('Eloquent JavaScript');
console.log(foundBook.getSummary());
// Output: 'Eloquent JavaScript by Marijn Haverbeke, 472 pages, ISBN: 1593275846'
console.log(foundBook.checkOut());
// Output: 'Eloquent JavaScript has been checked out.'
console.log(foundBook.checkOut());
// Output: 'Eloquent JavaScript is already checked out.'
console.log(foundBook.returnBook());
// Output: 'Eloquent JavaScript has been returned.'
Constructor Functions vs. Object Literals
When should you use constructor functions instead of simple object literals?
- Use object literals when you need a single object with unique properties
- Use constructor functions when you need to create multiple similar objects (instances)
// Object literal (single instance)
const car = {
make: 'Toyota',
model: 'Corolla',
year: 2020,
start: function() {
return 'Engine started';
}
};
// Constructor function (multiple instances)
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
Car.prototype.start = function() {
return 'Engine started';
};
const car1 = new Car('Toyota', 'Corolla', 2020);
const car2 = new Car('Honda', 'Civic', 2021);
Constructor Functions vs. ES6 Classes
ES6 introduced classes to JavaScript, which provide a more familiar syntax for developers coming from other languages. Under the hood, JavaScript classes still use constructor functions and prototypes.
// Constructor function approach
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hello, my name is ${this.name}`;
};
// Equivalent ES6 class
class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
While modern JavaScript often uses classes, understanding constructor functions is important because:
- You'll encounter them in older codebases
- They help you understand how JavaScript's object system works under the hood
- They provide insight into JavaScript's prototypal inheritance model
Summary
Constructor functions in JavaScript serve as templates for creating multiple objects with similar properties and methods. Key points to remember:
- Constructor functions are named with a capital letter by convention
- They must be called with the
new
keyword to create objects - Add methods to the prototype to improve efficiency
- They were the standard way to create object templates before ES6 classes
- They form the basis of JavaScript's prototypal inheritance system
Understanding constructor functions is essential for mastering JavaScript's object-oriented programming capabilities and gaining insight into how modern JavaScript classes work behind the scenes.
Exercises
-
Create a
Rectangle
constructor function that takeswidth
andheight
parameters and has methods to calculate area and perimeter. -
Create a
BankAccount
constructor function with methods for deposit, withdrawal, and checking the balance. Include validation to prevent overdrafts. -
Extend the Library example by adding a
Member
constructor function with checkout and return functionality that interacts with theLibrary
andBook
objects. -
Create a
Counter
constructor function that has methods to increment, decrement, and reset the count. -
Design a
TodoList
constructor function with methods to add tasks, mark tasks as complete, and list all tasks.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)