📖 JavaScript Constructors

In Object-Oriented Programming (OOP), constructors are a fundamental concept that helps initialize new objects. Understanding how constructors work is crucial for effectively creating and managing objects in your code.

Constructors

A constructor is a special method in a class that is automatically called when a new object is created. The primary role of a constructor is to initialize the object, setting up its initial state by assigning values to its properties.

Syntax of Constructors

Let's take a look at a simple constructor in a Student class.

class Student {
    constructor(name, studentID) {
        this.name = name;
        this.studentID = studentID;
    }
}
            

In this example, the constructor method constructor(name, studentID) initializes each new Student object with a name and a studentID.

The Role of Constructors in Object Initialization

Constructors are used to set up the initial values of an object's properties when it is created.

Setting Up Initial Values

When you create a new object, you can pass arguments to the constructor to set the initial state of the object.

const student1 = new Student("John Doe", "S12345");
console.log(student1.name); // Outputs: John Doe
console.log(student1.studentID); // Outputs: S12345
            

In this example, student1 is initialized with the name "John Doe" and the student ID "S12345".

Default Values

You can also set default values for properties within the constructor.

class Student {
    constructor(name, studentID = "Unknown") {
        this.name = name;
        this.studentID = studentID;
    }
}

const student2 = new Student("Jane Smith");
console.log(student2.studentID); // Outputs: Unknown
            

Here, if a studentID is not provided when the object is created, it defaults to "Unknown".

Working with Multiple Constructors

JavaScript classes don't support multiple constructors like some other programming languages. However, you can use conditional logic within a single constructor to achieve similar functionality.

Simulating Multiple Constructors

You can simulate multiple constructors by checking the type or existence of the constructor's parameters.

class Student {
    constructor(name, studentID) {
        if (typeof studentID === 'undefined') {
            this.name = "Default Name";
            this.studentID = "Unknown";
        } else {
            this.name = name;
            this.studentID = studentID;
        }
    }
}

const student3 = new Student();
console.log(student3.name); // Outputs: Default Name
console.log(student3.studentID); // Outputs: Unknown
            

In this example, if no arguments are passed, the constructor initializes the object with default values.

Constructor Overloading

Constructor overloading can be simulated using default parameters and conditional logic within the constructor.

class Student {
    constructor(name = "Default Name", studentID = "Unknown") {
        this.name = name;
        this.studentID = studentID;
    }
}

const student4 = new Student("Alice", "S67890");
console.log(student4.name); // Outputs: Alice
console.log(student4.studentID); // Outputs: S67890

const student5 = new Student();
console.log(student5.name); // Outputs: Default Name
console.log(student5.studentID); // Outputs: Unknown
            

Here, the constructor can handle different cases, either using the provided values or falling back to the defaults.

Constructors and Inheritance

When using inheritance, you can call the parent class's constructor from a subclass using the super() function. This allows the subclass to inherit and extend the properties of the parent class.

Example
class Person {
    constructor(name) {
        this.name = name;
    }
}

class Student extends Person {
    constructor(name, studentID) {
        super(name); // Calls the parent constructor
        this.studentID = studentID;
    }
}

const student6 = new Student("Bob", "S78901");
console.log(student6.name); // Outputs: Bob
console.log(student6.studentID); // Outputs: S78901
            

In this example, the Student class extends the Person class, inheriting its properties and adding a studentID.

Common Pitfalls with Constructors

Overusing Constructors

It's important to keep constructors focused on object initialization. Overloading a constructor with too many parameters can make it difficult to use and maintain. Consider using methods or factory functions for complex object creation.

Avoiding Side Effects

Constructors should be predictable and safe. Avoid performing actions with side effects, such as starting network requests or manipulating external resources, within a constructor. Keep the constructor's role limited to setting up the object's initial state.

Practical Examples and Use Cases

Let's build a more complete example using what we've learned about constructors.

class Course {
    constructor(courseName, credits = 3) {
        this.courseName = courseName;
        this.credits = credits;
    }
}

const course1 = new Course("Math 101");
const course2 = new Course("History 201", 4);

console.log(course1.courseName, course1.credits); // Outputs: Math 101 3
console.log(course2.courseName, course2.credits); // Outputs: History 201 4
            

In this example, the Course class uses a constructor to initialize the course name and the number of credits, with a default value for credits.

Summary

Constructor
A special method used to initialize new objects.
Default Parameters
Allows you to set default values for properties in the constructor.
Inheritance
Using super() in a subclass to call the parent class's constructor.
Avoiding Pitfalls
Keep constructors focused on initialization and avoid side effects.

Putting It Into Action

Let's build a simple Course Registration System to see how constructors can be used in practice. We'll define a Course class that allows us to create courses with a name, credits, and a default value for credits if none is provided.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Course Registration System</title>
</head>
<body>

    <h1>Course Registration System</h1>

    <h2>Add a New Course</h2>
    <form id="addCourseForm">
        <label for="courseName">Course Name:</label>
        <input type="text" id="courseName" name="courseName"><br><br>

        <label for="credits">Credits:</label>
        <input type="number" id="credits" name="credits" placeholder="Default: 3"><br><br>

        <button type="submit">Add Course</button>
    </form>

    <h2>Course List</h2>
    <ul id="courseList"></ul>

    <script>
        // Define the Course class with a constructor
        class Course {
            constructor(courseName, credits = 3) {
                this.courseName = courseName;
                this.credits = credits;
            }

            getDetails() {
                return `${this.courseName} - Credits: ${this.credits}`;
            }
        }

        // School class to manage courses
        class School {
            constructor() {
                this.courses = [];
            }

            addCourse(course) {
                this.courses.push(course);
            }

            listCourses() {
                return this.courses;
            }
        }

        const school = new School();

        document.getElementById('addCourseForm').addEventListener('submit', function(event) {
            event.preventDefault();
            const courseName = document.getElementById('courseName').value;
            let credits = document.getElementById('credits').value;

            if (!credits) {
                credits = 3; // Apply default value if credits is empty or zero
            }

            const newCourse = new Course(courseName, credits);
            school.addCourse(newCourse);

            updateCourseList();
        });

        function updateCourseList() {
            const courseList = document.getElementById('courseList');
            courseList.innerHTML = '';

            school.listCourses().forEach((course) => {
                const li = document.createElement('li');
                li.textContent = course.getDetails();
                courseList.appendChild(li);
            });
        }
    </script>
</body>
</html>                

In this example, we've defined a Course class with a constructor that initializes the course name and credits. If no credits are provided, the default value of 3 is used. We then create a simple form that allows users to add new courses to the list, demonstrating how constructors can be used in a real-world application.

Challenge

Now that you've built a basic Course Registration System, try extending its functionality with the following challenge:

  • Add a Course ID property to the Course class and modify the constructor to initialize this property.
  • Generate a unique Course ID for each course added to the system.

In order to check your learning, you should attempt to create a solution before revealing the provided solution below.


// JavaScript Code for the Challenge Solution

class Course {
    constructor(courseName, credits = 3) {
        this.courseName = courseName;
        this.credits = credits;
        this.courseID = `C${Math.floor(Math.random() * 10000)}`; // Generate a random Course ID
    }

    getDetails() {
        return `${this.courseID}: ${this.courseName} - Credits: ${this.credits}`;
    }
}

// The School class and the rest of the code remain unchanged

function updateCourseList() {
    const courseList = document.getElementById('courseList');
    courseList.innerHTML = '';

    school.listCourses().forEach((course) => {
        const li = document.createElement('li');
        li.textContent = course.getDetails();
        courseList.appendChild(li);
    });
}
                

References