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/
Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are also lots of tricky parts to JavaScript. We can follow some best practices easily to make our JavaScript code easy to read.
In this article, we’ll look at replacing IIFE with modules and blocks, replacing traditional functions with class methods and arrow functions, removing language attribute from script
tags, writing pure functions, and avoiding a long list of arguments.
Replacing IIFE with Modules and Blocks
IIFE stands for Immediately Invoked Function Expression. It’s a construct where we define a function and then call it immediately.
It’s a popular way to isolate data to prevent them from being accessed from, the outside. Also, it hides them from the global scope.
This is handy before ES6 because there were no standards for modules and there's also no easier way to hide things from the global scope.
However, with ES6, modules are introduced as a new feature. This means that we can replace something like:
(function() {
let x = 1;
})()
With a module that has:
let x = 1;
If we don’t want it to be available outside the module, we just don’t export it.
With modules, there’s also no need for namespacing with IIFEs since we can put modules in different folders to separate them from each other.
Also, another new feature of ES6 is blocks. Now we can define blocks of code segregated from the outside scope by using curly braces as follows:
{
let x = 1;
console.log(x);
}
With block-scope keywords like let
or const
for variable or constant declaration, we don’t have to worry about things that we don’t want to be accessed outside from being accessed.
Now we can define arbitrary blocks without if
statements, loops, or IIFEs.
Replace Traditional Functions with Arrow Functions and Class Methods
Again, with ES6, we have the class syntax for constructor functions. With that, we don’t need the function
keyword to create constructor functions.
It makes inheritance much easier and there’s no confusion about this
.
In addition, we have arrow functions that don’t change the value of this
inside the function.
The only reason left to use the function
keyword is for generator functions, which are declared with the function*
keyword.
For example, we can create a simple generator function by writing the following:
const generator = function*() {
yield 1;
yield 2;
}
Note that generator functions can only return generators so we can’t return anything else from it.
From the Language Attribute From Script Tags
The language
attribute no longer has to be included with script
tags.
For example, instead of writing:
<script src="[https://code.jquery.com/jquery-2.2.4.min.js](https://code.jquery.com/jquery-2.2.4.min.js)" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous" language='text/javascript'></script>
We write:
<script src="[https://code.jquery.com/jquery-2.2.4.min.js](https://code.jquery.com/jquery-2.2.4.min.js)" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
This is because JavaScript is the language left that runs in browsers.
Photo by Ambitious Creative Co. - Rick Barrett on Unsplash
Write Pure Functions
We should write functions as pure functions. That is functions that always gives the same output given that we pass in the same input.
This is important because it makes testing easy. Also, we know what it’ll do exactly, reducing the chance of bugs.
Pure functions are easy to read and understand. The flow is determinate and simple. Therefore, it’s predictable.
An example of a pure function would be the following:
const add = (a, b) => a + b;
The add
function always givens the same output if we give it the same inputs since it just computes the results from the parameters. It doesn’t depend on anything else.
Therefore, the logic flow of it is very predictable.
An example of a function that’s not a pure function would be one that returns different output even if the same inputs are given. For example, if we have a function to get the year number that is x
years before this year:
const getYearBefore = (x) => new Date().getFullYear() - x;
This wouldn’t be a pure function because new Date()
changes according to the current date. So the result of getYearBefore
depends on the current date’s year given the same input.
To make it a pure function we can instead write:
const getYearBefore = (date, x) => date.getFullYear() - x;
Since the date is now passed into the function, we get the same results given that we have the same input since nothing in our function is non-deterministic. It’s just combining the parameters together and returning the result.
Also, we don’t have to worry about the current date or how the date implementation changes.
Extending the function is hard before we changed it to a pure function because of the unpredictability of the result of new Date()
.
In addition, changing new Date()
may break other parts of the program before out change. Now we don’t have to worry about that.
Because of the unpredictability, it’s also harder to trace and debug.
Avoid Long Argument List
With the destructuring syntax of ES6, we can pass in lots of arguments to a function without actually passing them all in separately.
For example, instead of writing:
const add = (a, b, c, d, e) => a + b + c + d + e;
which has 5 parameters. We can write:
const add = ({
a,
b,
c,
d,
e
}) => a + b + c + d + e;
This way, we can just pass in one object into the function and get 5 variables inside the add
function.
We can call add
as follows:
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
}
add(obj);
This also works for nested objects. For example, we can write:
const buildString = ({
foo: {
bar,
baz
}
}) => `${bar} ${baz}`;
Then we can call buildString
as follows:
const obj = {
foo: {
bar: 'bar',
baz: 'baz'
}
};
buildString(obj);
To keep data private, we can use modules and blocks to replace IIFEs. Now we don’t need extra code to define functions and call it.
Also, the class syntax is a much clearer way to define constructors, especially when we want to inherit from other constructors. The value of this
is also clearer.
Writing pure functions is a good practice because it’s predictable since we always have the same outputs given the same inputs. It’s also easier to read and test because of it.
With the destructuring syntax, we can reduce lots of arguments to variables in an object. Everything is assigned automatically given the key name and position of the properties we pass in as the argument.