DEV Community

b.chau504
b.chau504

Posted on • Updated on

Factory Functions vs Constructors

To grasp the concept of factory functions and constructors, you must first understand functions and objects.

If you're creating numerous amounts of objects with similar properties and functionality, it can be tedious and repetitive. We want to keep in mind the principle of keeping our code D.R.Y., which stands for "Don't Repeat Yourself". There are function patterns that can help us write shorter, cleaner, more efficient code. Factory functions and constructor functions can do just that, but which one should you use? Let's dive a little deeper into what factory functions and constructors are, what they can and can't do, and the similarities and the differences.

What are factory functions?

Factory functions can be thought of as an actual factory that takes in raw materials and produces numerous products promptly. Factory functions, on the other hand, take in specific inputs and uses those inputs to create a new object. So, how can this actually be useful to us? We can simply create each object individually, but that might take some time. If you are creating objects with the same properties and different values, then creating one factory function can make this process quicker.

const car1 = {
    make: 'Toyota',
    model: 'Tacoma',
    year: 2018,
    fuelType: 'gas', 
    bodyType: 'mid-size pick-up truck',
    getFullName() {
        return `${this.year} ${this.make} ${this.model}`;
    }
}

console.log(car1.getFullName()); // => 2018 Toyota Tacoma
Enter fullscreen mode Exit fullscreen mode

In the example above, we create an object describing a specific car. Now let's make a similar object to this one.

const car2 = {
    make: 'Tesla', 
    model: 'Model S',
    year: 2018,
    fuelType: 'electric',
    bodyType: 'sedan',
    getFullName() {
        return `${this.year} ${this.make} ${this.model}`;
    }
}

console.log(car2.getFullName()); // => 2018 Tesla Model S 
Enter fullscreen mode Exit fullscreen mode

Now, I can continue creating objects of more cars, but who has time for that, right? Let's see what this would look like as a factory function.

function createCar (make, model, year, fuelType, bodyType) {
    return {
        make: make, 
        model: model, 
        year: year, 
        fuelType: fuelType, 
        bodyType: bodyType,
        getFullName() {
            return `${year} ${make} ${model}`;
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

We now have a factory function that creates new objects for us. Now, all we have to do is pass in data and let the factory function do its thing.

function createCar (make, model, year, fuelType, bodyType) {
    return {
        make: make, 
        model: model, 
        year: year, 
        fuelType: fuelType, 
        bodyType: bodyType,
        getFullName() {
            return `${year} ${make} ${model}`;
        }
    }
}

const car1 = createCar('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = createCar('Tesla', 'Model S', 2018, 'electric', 'sedan');

console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S 
Enter fullscreen mode Exit fullscreen mode

You may build any number of objects by utilizing the factory function instead of repeating code.

What are constructors?

A constructor function is another javascript pattern, that is very similar to factory functions. Though, unlike factory functions, constructor functions do not actually return an object. To create different objects with the same properties, we would need to use the keyword "new" and the keyword "this". With that being said, let's see how constructor functions work visually.

function Car(make, model, year, fuelType, bodyType) {
    this.make = make
    this.model = model 
    this.year = year
    this.fuelType = fuelType
    this.bodyType = bodyType
    this.getFullName = () => {
        return `${this.year} ${this.make} ${this.model}`;
    }
}
Enter fullscreen mode Exit fullscreen mode

In the example above, the constructor function looks a lot like a factory function, except for the use of the keyword "this"."this" refers to the constructor function that created the instance object. In other words, "this" has no value in a constructor function. You can think of it as a stand-in for the new object.

function Car(make, model, year, fuelType, bodyType) {
    this.make = make
    this.model = model 
    this.year = year
    this.fuelType = fuelType
    this.bodyType = bodyType
    this.getFullName = () => {
        return `${this.year} ${this.make} ${this.model}`;
    }
}

const car1 = new car('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = new car('Tesla', 'Model S', 2018, 'electric', 'sedan');

console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S
Enter fullscreen mode Exit fullscreen mode

To create objects using constructor functions, we use another keyword, "new". When you use the term "new" in front of a function call, javascript does two things for us automatically:

  1. Inside the function, creates an empty object with the name "this".
  2. Returns the object "this" to the statement that called the function initially.
function Car(make, model, year, fuelType, bodyType) {
    // const this = {};
    this.make = make
    this.model = model 
    this.year = year
    this.fuelType = fuelType
    this.bodyType = bodyType
    this.getFullName = () => {
        return `${this.year} ${this.make} ${this.model}`;
    }
    // return this;
}

const car1 = new car('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = new car('Tesla', 'Model S', 2018, 'electric', 'sedan');
console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S
Enter fullscreen mode Exit fullscreen mode

Inheritance

Inheritance plays a big part in how factory functions and constructor functions are different.

function createCar (make, model, year, fuelType, bodyType) {
    return {
        make: make, 
        model: model, 
        year: year, 
        fuelType: fuelType, 
        bodyType: bodyType,
        getFullName() {
            return `${year} ${make} ${model}`;
        }
    }
}

const car1 = createCar('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = createCar('Tesla', 'Model S', 2018, 'electric', 'sedan'); 

car1.getFullName = function() {
    return `My ${fuelType} ${bodyType} is a ${year} ${make} ${model}`;
}

console.log(car1.getFullName()); // => My gas mid-size pick-up truck is a 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S
Enter fullscreen mode Exit fullscreen mode

Let's refer back to the first example of the factory function. What if we wanted to redeclare car1.getFullName()? Well, car1.getFullName() and car2.getFullName() are not the same function in memory. Each object gets its own copy of the function. Meaning, that when the function creates an object and returns it, it copies the properties and values and attaches them to every object calling the function.

function car(make, model, year, fuelType, bodyType) {
    // const this = {};
    this.make = make
    this.model = model 
    this.year = year
    this.fuelType = fuelType
    this.bodyType = bodyType
    this.getFullName = () => {
        return `${this.year} ${this.make} ${this.model}`;
    }
    // return this;
}

const car1 = new car('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = new car('Tesla', 'Model S', 2018, 'electric', 'sedan');

console.log(car1); // => car {make: 'Toyota', model: 'Tacoma' , etc.}
Enter fullscreen mode Exit fullscreen mode

Now, let's take a look at our constructor function above. When a constructor is made, it comes with its own prototype. When we create a new car object using the "new" keyword, it creates an instance of the car type. In other words, car1's prototype is of type "car". Now, car1 is inheriting from the car constructor. This allows us to add properties to the prototype car.

Car.prototype.sentence = function() {
    return `My ${this.fuelType} ${this.bodyType} is a ${this.year} ${this.make} ${this.model}`;
}
Enter fullscreen mode Exit fullscreen mode
console.log(car1); // => Car {
//   make: 'Toyota',
//   model: 'Tacoma',
//   year: 2018,
//   fuelType: 'gas',
//   bodyType: 'mid-size pick-up truck',
//   getFullName: [Function (anonymous)]
// }
Enter fullscreen mode Exit fullscreen mode

Notice that the sentence function isn't directly added to the car constructor itself.

console.log(Car.prototype); // => { sentence: [Function (anonymous)] }
Enter fullscreen mode Exit fullscreen mode

But when we check its prototype, there it is! Now we're able to access the newly added function.

console.log(car1.sentence()); // => My gas mid-size pick-up truck is a 2018 Toyota Tacoma
console.log(car2.sentence()); // => My electric sedan is a 2018 Tesla Model S
Enter fullscreen mode Exit fullscreen mode

Now that we know how each operation works, the question is: Which one should we use? They can both get you the same result. Constructors are great when you want to add or remove a property from every object inheriting from the constructor. However, factory functions can be more straightforward to understand, because, at the end of the day, it's just a function. For factory functions, we don't have to involve the keyword "new". It can be more flexible, with the power of closure. With that, we can accomplish something called "data privacy". Let's look at one more example explaining how this works.

function createCar(make, model, year) {
    return {
        getFullName() {
            return `${year} ${make} ${model}`;
        }
    }
}

const car1 = createCar('Toyota', 'Tacoma', 2018); 

console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car1.make); // => undefined
console.log(car1); // => { getFullName: [Function: getFullName] }
Enter fullscreen mode Exit fullscreen mode

car1.make is hidden and it cannot be reached or found in the prototype. This makes it more difficult to accidentally create a bug somewhere in your code.

Conclusion

All in all, the great thing about writing your own code is nothing is necessarily wrong. There is a good practice of keeping our code clean and short. Use factory functions and constructor functions where you think it would be best for your case.


Sources

Top comments (2)

Collapse
 
abhishekgarfield profile image
xyz

' unlike factory functions, constructor functions do not actually return an object. '
' Returns the object "this" to the statement that called the function initially. '

Can someone please explain these 2 lines because what i concluded is constructor function also returns an object named this

Collapse
 
lysenkodenys profile image
Denys

Why in Inheritance section the name of constructor function (car) is not start from Capital character?