Cleaning up our JavaScript code is easy with default parameters and property shorthands.
In this article, we’ll look at the best practices for creating classes and when we should create them.
Constructors
There are a few things that we should do to make our constructors better. They are the following.
Initialize All Member Data in All Constructors If Possible
We should put them all in the constructor so that they’re all initialized when we instantiate the object.
So we can write:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
Now we make sure that everything’s initialized with a value.
Create a Singleton In the Constructor
If we need only one instance of a constructor, then we can create one instance of it.
For instance, we can write the following:
class Person {
constructor(name) {
if (this.instance) {
this.instance = {
name
}
}
return this.instance;
}
}
In the code above, we return the object that we created if this.instance
isn’t defined yet.
Otherwise, we return whatever it’s set to this.instance
.
Prefer Deep Copies to Shallow Copies Until Proven Otherwise
Deep copies copy everything, so that’s a lot better than doing a shallow copy. Shallow copies leave some things referencing the original object.
That’s not good if we want a true copy.
Therefore, we’ve to make our code to make deep copies as follows:
const copy = obj => {
const copied = {
...obj
};
for (const k of Object.keys(obj)) {
if (typeof obj[k] === 'object') {
copied[k] = {
...copied[k]
};
copy(copied[k]);
}
}
return copied;
}
We just use the spread operator to copy nested objects if one is found. And do the same thing recursively.
Then we return the object that we copied.
When Should We Create a Class?
We shouldn’t always create classes. There a few scenarios where it makes sense to create a class.
Model Real-World Objects
Classes are great for modeling real-world objects since they model the behavior of objects
They let us encapsulate instance variables and methods into one package to store state and do actions on objects respectively.
Model Abstract Objects
Likewise, we can use classes to model abstract objects.
They can be used to make abstractions, which are generalizations of different kinds of objects.
Classes are great for holding shared members of subclasses. And subclasses can inherit from them.
However, we should keep the inheritance tree simple so that people won’t be confused with the code.
Reduce Complexity
We can use classes to reduce the complexity of a program.
Classes are great for hiding information. In JavaScript, there’re no private variables in classes yet, so we’ve to hide data in methods instead.
We can then minimize coupling between different parts of our program with that.
Hide Implementation Details
Methods are also good for hiding implementation details.
We can hide the details within methods and only run things that are needed.
To do that, we can nest functions and variables inside methods.
Limit Effects of Changes
The effects of changes can be reduced since we can hide things.
As with hiding implementation, the effects of changes can be isolated by limiting the effects of changes within methods.
Hide Global Data
Global data can become private data by putting them inside the methods of a class.
Then they don’t have to be exposed to the public. All we have to do is to use let
and const
to declare them within methods.
Streamline Parameter Passing
If we have the same parameters passed into different functions, then we can change the parameters to instance variables and the functions to methods.
For instance, if we have:
const speak = (name) => `${name} spoke`;
const greet = (name) => `hi, ${name}`;
Then we can put the methods into their own class as follows:
class Person {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} spoke`;
}
greet() {
return `hi, ${this.name}`;
}
}
Now we don’t have to pass in name
everwhere.
We just make an instance of Person
and call those methods without passing in any arguments.
Conclusion
We can create classes to encapsulate data and package things together. However, we shouldn’t create classes for everything.
Also, we should make deep copies rather than shallow copies whenever possible.