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 to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to write a piece of clean JavaScript code.
In this article, we’ll look at how to refactor our JavaScript functions so that it’s clean and easy to read.
Refactor Constructors to Classes
In JavaScript, a class is just syntactic sugar for constructor functions, so they’re actually functions.
All classes do is provide a more intuitive syntax for constructor functions by avoiding dealing with prototypes and using call
to call parent constructor functions when we extend another constructor.
For instance, instead of writing:
function Foo(name) {
this.name = name;
}
Foo.prototype.getName = function() {
return this.name;
}
We write:
class Foo {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
As we can see, with the class syntax, it’s clear where the constructor is and what instance methods our class contain with the class syntax.
Instead of attaching instance methods to the prototype of the constructor function, we add it to the class.
We also don’t have to write out the keyword function
all the time, which makes typing shorter while keeping the code easy to understand. There’s no ambiguity with this syntax.
If we want to do inheritance, instead of writing:
function Animal(name) {
this.name = name;
}
Animal.prototype.getName = function() {
return this.name;
}
function Cat(name) {
Animal.call(this, name);
}
Cat.prototype.constructor = Animal;
We write:
class Animal {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
This way, we get an error is we forgot to call the parent’s constructor, which we don’t get when we forgot to call the parent constructor in the constructor function example.
We also make it clear that we’re extending the Animal
class with the extends
keyword instead of having to set the parent constructor in the child constructor ourselves.
Avoiding Traditional Functions As Much As Possible
Traditional functions were used for everything before ES2015. It’s used for encapsulating code, used as blocks, used for constructors as we have above, used for callbacks, etc.
Most of these cases have been replaced by other constructors that were introduced with ES2015.
If we want to encapsulate code, we can use blocks. For instance, instead of writing an immediately invoked function expression (IIFE) as follows:
(function() {
let x = 1;
console.log(x);
})()
We can instead write:
{
let x = 1;
console.log(x);
}
The 2nd example is shorter and we only have to delimit the block with the curly braces.
The variable x
isn’t available outside the block with the code above.
Another way to encapsulate code is to use modules. For instance, we can write the following code:
module.js
export const x = 1;
const y = 2;
const z = 3;
export default y;
index.js
import { x } from "./module";
import y from "./module";
console.log(x, y);
In the code above, we only expose what we have when exporting them with the export
keyword. Therefore, x
and y
are available for import from another module.
As we can see, x
and y
were imported from module.js
. But z
couldn’t be because it wasn’t exported.
Therefore, we don’t need IIFEs like the following anymore:
const module = (function() {
const x = 1;
const y = 2;
const z = 3;
return {
x,
y
}
})();
The code above is longer and it uses a function unnecessarily. Also, as the function has more members, it’ll get longer, and it isn’t a good idea to have long functions.
For callbacks, we can use arrow functions instead. For instance, instead of writing:
const arr = [1, 2, 3].map(function(a) {
return a * 2;
})
We should write:
const arr = [1, 2, 3].map(a => a * 2)
It’s shorter, so we don’t have to type as much as we don’t have to type our the function
keyword. Also, the return is implicit for one lined arrow functions. And we also don’t have to worry about the value of this
and arguments
since arrow functions don’t bind to any of these values.
Conclusion
To refactor functions, we should convert constructors to classes. Otherwise, we should convert them to blocks, modules, and arrow functions as we see fit.