JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.
In this article, we’ll look at some best practices when developing in JavaScript, including object creation and constructors.
Prototypes
When we have methods in our constructor, we should add them to the prototype
property of the constructor instead of putting it inside the constructor function.
For instance, instead of writing:
function Person(name) {
this.name = name;
this.greet = function() {
return `hi ${this.name}`;
}
}
We should write:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `hi ${this.name}`;
}
Even better, we use the class syntax by writing:
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `hi ${this.name}`;
}
}
The last 2 pieces of code are the same.
We want to write our constructors like the last 2 examples because we only want to share one copy of the greet
method between all the instances.
The greet
method doesn’t change, only the value of this
does.
Therefore, we don’t want to keep different copies of the same method as we would do in the first example.
Instead, we want to share it as we do in the last 2 examples.
We can create an instance of the constructor to find the difference:
const person = new Person('joe');
console.log(person)
If we log the person
object, we can see that the method is in the __proto__
property, which is the prototype of the person
.
It has the greet
method.
If we create the same object with the version first version of the Person
constructor, we’ll see that the greet
method is in the person
object itself.
Therefore, to save memory, the method should be in the constructor’s prototype
property rather than inside the constructor itself.
Constructor’s Return Values
Constructors don’t have to return something explicitly.
We usually shouldn’t return anything explicitly because it’ll return the instance of the constructor automatically.
However, it does let us return something different if we want.
But we usually shouldn’t do that unless we have a reason to.
If we want to do that we can write:
function Person(name) {
this.name = name;
return {
foo: name
}
}
Then when we instantiate it as follows:
const person = new Person('joe');
console.log(person)
We get {foo: “joe”}
instead of Person {name: “joe”}
according to the console log output.
Patterns for Enforcing new
To create a new instance of an object, we need to use new
.
The best way to do that is to use the class syntax.
This way we can’t call it without new
.
If we call a constructor without new
, then this
would be the global object.
Naming Convention for Constructors/Classes
Constructors or classes are usually named in PascalCase.
This distinguishes regular functions from constructors and classes.
Using that
If we want to make sure that our constructor always behaves as a constructor, we can also create an object and return that.
For instance, we can write:
function Person(name) {
const that = {};
that.name = name;
return that;
}
Now if we call our Person
constructor with our without new
, we still get an object returned.
Self-Invoking Constructor
We may also use the instanceof
operator to check if a constructor’s this
is the instance of the constructor.
Then we’ll know if we called it with new
or not.
If we did, then the constructor’s this
should be an instance of the constructor.
For instance, we can check as follows:
function Person(name) {
this.name = name;
if (!(this instanceof Person)) {
return new Person(name);
}
}
Now whether we called the Person
constructor with new
or not, we get the same result.
const person = new Person('joe');
and:
const person = Person('joe');
should get us the same result.
Conclusion
We should keep our methods in the prototype of the constructor rather than inside the constructor itself.
This way, one copy of the method is shared among all the instances.
This works because the method is the same across all instances.
The class syntax does this for us automatically.
Also, we can check if this
is an instance of the constructor to check if our constructor has been called properly.
The class syntax also takes care of that for us since we can’t invoke a class without the new
keyword.