Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
JavaScript is an easy programming language to learn. It’s easy to write programs that run and do something. However, it’s hard to account for all the use cases and write robust JavaScript code.
In this article, we’ll look at some best practices for writing robust JavaScript code.
Use Factory Functions
Factory functions are functions that return a new instance of a constructor or class.
We can use them to create objects without writing any code with the new
keyword to instantiate classes or constructors.
Constructors are often a confusing part of JavaScript and definitely one we can avoid if we wish to.
For instance, we can make our own factory function as follows:
In the code above, we have the createPerson
factory function that takes a firstName
and lastName
parameter and returns an object with the firstName
, lastName
, and the fullName
method.
We used an arrow function so that we don’t get confused with the value of this
that’s in the returned object. Since arrow functions don’t bind to this
, we know that the value of this
in fullName
is the returned object.
Factory functions can return objects with anything, so the possibilities are endless.
To use our createPerson
factory function, we can write the following code:
const person = createPerson('Jane', 'Smith');
const fullName = person.fullName();
We created a person
object with the createPerson
function and then called the fullName
method on it.
Then we get that fullName
is ‘Jane Smith’ since this
references the person
object.
Many JavaScript functions like Number
and Boolean
are factory functions. They take any object as an argument and then return a number or boolean, respectively.
It makes our JavaScript code more robust because we don’t have to worry about class or constructor instances. We just have to think in terms of objects and composing functions.
Composing functions makes code more reusable and testable, creating more robust code because of this.
Create Instance Methods for Constructors by Attaching to the prototype Property
When we want to create instance methods of a constructor, we should attach the methods to the prototype
property of the constructor.
This way, when we instantiate the constructor, we can also call the methods on the instance.
A method that’s attached directly to the function is a static method that’s shared by all methods.
For instance, we can add instance methods to a constructor by attaching it to its prototype
property as follows:
In the code above, we created a Fruit
constructor that returns a Fruit
instance. Then we added the grow
instance method by writing:
Fruit.prototype.grow = function() {
console.log(`${this.name} grew.`);
}
Then when we instantiate it as follows:
const fruit = new Fruit('apple');
fruit.grow();
Instance methods encapsulate methods inside the constructor instance, preventing it from being exposed to outside code and allowing it to make accidental changes.
Therefore, this makes our code more robust. Also, every constructor has its own responsibility since it has its own instance method. This prevents multiple constructors from doing different things to the same object.
The alternative (above) creates a copy of the grow
instance method for each instance. The method isn’t cached, so it’s slower to use attach instance methods this way.
Use the .type Property to Create Factory Functions That Can Create Multiple Types of Objects
Since factory functions can be composed, we can create a factory function that can create multiple types of objects.
We can differentiate between them with the type
property, which is a conventional way to distinguish between different types of objects that we want to create.
To do this, we can write the following code:
In the code above, we have the createPet
factory function that calls the createDog
or createCat
factory function depending on what kind of pet type
we pass into createPet
.
We also composed factory functions by calling a factory function inside the createPet
factory function.
Now we just have to worry about one factory function (createPet
) instead of using multiple factory functions to create different kinds of objects. This hides the complexity that’s underneath the code and thus there’s less risk of breaking things when we make changes.
Conclusion
With factory functions, we can create them to create new objects. We can also compose them and call different ones within one factory function to do different things.
When we have a constructor function, we should attach instance methods to the prototype
property of the constructor function so that they don’t have to be recreated all the time.