How to Write JavaScript the Right Way (With Examples)
Posted
JavaScript is my favourite language of all time. It’s easy to learn but hard to master language.
Because it’s hard to master, today I am presenting to you 9 ways to make your JavaScript look more professional.
Use let and const instead of var keyword
One thing that you will see in every JavaScript style guide is not to use var
keyword for declaring variables.
Variables declared with var will be hoisted to the top of the function scope. That means it will be visible at the top of the scope where you reference it. Did you lose me? Maybe this can help.
var answer = 42; // This is what we see
var answer;
answer = 42; // This is what the computer sees
One more issue is the ability to declare the same identifier more than one time. That can cause a lot of issues in large codebases or if you are reusing the same variable names.
let
and const
prevent this. They are designed to be block-scoped which means they belong to the scope where they are declared. I think this is best demonstrated with some good old code.
Here is a quick demonstration. What do you think will be the output of this piece of code?
let arr = [];
(function() {
for (var i = 0; i < 3; i++) {
arr.push(function() {return i})
}
}
)();
arr.forEach(func => console.log(func()))
That’s right! It’s [3,3,3]
. Wait, what? This doesn’t make any sense. It’s not obvious but remember that I just said that var hoists variable declaration to the top of the scope? Here is what actually happened?
let arr = [];
(function() {
var i;
for (i = 0; i < 3; i++) {
arr.push(function() {return i})
}
}
)();
arr.forEach(func => console.log(func()))
But wait? Shouldn’t arr
contain three functions where first returns 1
, second 2
and third 3
?
No.
Why?
Because it’s referencing i
in function scope not i
in the for
loop scope.
How can we fix this?
Change var
to let
.
let arr = [];
(function() {
for (let i = 0; i < 3; i++) {
arr.push(function() {return i})
}
}
)();
arr.forEach(func => console.log(func()))
Now we get [1,2,3]
.
If var
hoists variable declaration to the top of the function, what will happen if we use it in global scope?
Excellent question. It will be attached to the window
object. Can you now see if for e.g. you install some library and it attaches animation
variable to window
and somewhere else you declare variable animation
? Long story short, it would be a nightmare to debug.
In conclusion, if you plan on not changing the value of a variable you should declare it with const
. If you will change the value of the variable you should use let
.
Note that mutating properties of an object or adding and removing elements from the array will not be considered changing the value since array and object are reference type so use const
there too.
Use arrow function syntax.
Before we start talking about my favourite feature of ES6 also known as Ecmascript2015, I need to tell you the difference between function expression and function statement.
const functionExpression = function() {
return 'Function Expression';
}
function functionStatement() {
return 'Function Statement';
}
console.log('This is', functionExpression());
console.log('This is', functionStatement());
They look the same right? Well, almost. You just need to remember that function statements are hoisted and function expressions are not hoisted. And function expression have name
property but that’s not too important.
With Ecmascript2015 we got a new way to write our function expressions. Many developers think that arrow functions are just syntactic sugar for function keyword but that’s not the case.
First, let’s get over some of the forms of new arrow functions with few examples.
const addTwoES5 = function(number) {
return number + 2;
}
const addTwoES6 = (number) => {
return number + 2;
}
Well this doesn’t look that cleaner, doesn’t it? Why don’t we trim some of the parts we don’t need?
const addTwoES6 = number => number + 2;
How about now? With arrow functions, we have implicit returns in cases where we need just one line to write it.
How would we return an object? I’m glad you asked. Here is how.
const giveMeACat = () => ({name: 'Lazarus', cuteness: 9001});
One more way to use arrow function without arguments is to put _
instead of ()
.
This looks cool but why should I bother?
Have you ever had a problem this
keyword? No? Then you should start learning JavaScript. It’s an awesome language.
Arrow functions don’t have their own this
. Unlike functions declared with function
keyword where this
keyword refers to the scope of that function, arrow functions retain this
of the caller’s scope.
Let’s say you have Pets
object with two properties called owner
and names
and you want to implement pet
method. We don’t want to complicate things so we write it like this.
const Pets = {
owner: 'Kodeblok',
names: ['Lazarus', 'Gopher'],
pet: function() {
console.log(this.owner + ' is petting ' + this.names)
}
}
Pets.pet();
OK, nothing too strange. We get Kodeblok is petting Lazarus, Gopher
as expected. But let’s give each of the pet some more attention. Let’s say the owner wants to give attention to every of its pets. No problemo. We just need some refactoring magic and we are all set.
const Pets = {
owner: 'Kodeblok',
names: ['Lazarus', 'Gopher'],
pet: function() {
this.names.forEach(function (name) {
console.log(this.owner + ' is petting ' + name)
})
}
}
Pets.pet();
Aaand this.owner
is undefined
. That’s because this
is referring to anonymous functions this
not Pet
this
.
Arrow functions to the rescue!
const Pets = {
owner: 'Kodeblok',
names: ['Lazarus', 'Gopher'],
pet: function() {
this.names.forEach(name => console.log(this.owner + ' is petting ' + name))
}
}
Pets.pet();
And now everyone is happy. Yay!
Should I always use arrow functions?
Of course not. For e.g., if we used arrow function with our first example we would get undefined
. You can try it if you want.
You must use functions statements for object methods, generator functions or if you want to attach a function on window
object.
But use arrow functions for everything else.
== is not equal to ===
==
is an equal operator and we use it to compare values.
Pretty simple, right?
So, 77 == 77
will return true because they are same? Correct.
That means 77 == '77'
should be false
because 77
is a number and '77'
is a string, right? Well, no.
You see, when we pass two values of a different type to ==
operator, it tries to cast them to a common type. In this case
it can cast '77'
in 77
so we get true.
===
operator prevents this. ===
is strict comparison operator. It will check if both the value AND type are the same.
To keep this section short, just always use ===
because there is no reason to use ==
.
Learn to Iterate the Right Way
Even if you don’t know JavaScript, if you ever programmed in your life you are probably familiar with the concept of for
loop.
Before ES6+ we used for loops to iterate over arrays like this:
const someArray = ['Hello', 'World', "!"];
for (let i = 0; i < 3; i++) {
console.log(someArray[i]);
}
But there are more ways to the same thing and make your code look more idiomatic.
Let’s start with forEach()
method.
forEach
is an array method that takes a callback function and passes each array element to that callback. It can also pass two more arguments: the index of the current element and array that we are currently iterating over.
So our previous example will look something like this:
const someArray = ['Hello', 'World', "!"];
someArray.forEach(element => console.log(element));
// Or if you want it all on one line
['Hello', 'World', "!"].forEach(element => console.log(element));
This does look cleaner, doesn’t it?
One more benefit with this approach is that we are eliminating off-by-one error.
Next up we have for...in
and for...of
. For more than I am willing to admit I thought these two were the same thing.
The for…in statement iterates over the enumerable properties of an object, in an arbitrary order.
The for…of statement iterates over values that the iterable object defines to be iterated over.
Translated in English this means that we are using for...in
to iterate over properties of an object (remember that Array is also Object) and for...of
over values of an iterable.
I didn’t mention this up to now but iterables can be anything that contains Symbol.iterator
. This can be a post all for itself so for simplicity sake when I write iterable think array, okay?
Here is an example that should make these concepts a bit clearer:
Array.prototype.hotel = 'Trivago';
const importantNumbers = [43,69,420];
for (let number in importantNumbers) {
console.log(number); // 0, 1, 2, hotel
}
for (let number of importantNumbers) {
console.log(number); // 43, 69, 420
}
With for...in
we iterated over properties of Array
object. You can see how this can be useful when we need to iterate over keys of an object.
for...of
just iterated over values of our iterable. For those who know what an iterator
is it just called iterator.next()
bunch of times until internal done
variable wasn’t true
.
So, when should you use what method for iteration?
Use forEach
when you need to iterate over the whole array and use for...of
when you need to use continue
or break
somewhere. And of course use for
and for...in
when you have a good reason to but I’d stay away from them.
Use async/await If You Need to Nest Promises
If you ever had a pleasure to work with callbacks you will know that Promise API was sent from heaven. But Promises still can get messy from time to time. Like this for example:
const firstPromise = new Promise((res, rej) => res());
const secondPromise = new Promise((res, rej) => res());
const thirdPromise = new Promise((res, rej) => res());
firstPromise
.then(() => {
secondPromise.then(() => {
thirdPromise.then(() => {
console.log('Hello, World!')
})
})
})
.catch(err => console.error(err));
Introducing async/await
syntax. Next-generation of asynchronous JavaScript syntax.
async
is a keyword that we put in front of a function in which we want to use await
keyword. I have to point out that async functions will always return a Promise
even if it’s a void function.
await
keyword is used in an async function to declare variables whose values are resolved values of other Promises. That means the function will block until Promise
that we are awaiting (see what I did there) to resolve.
That turns our previous example in this:
const firstPromise = new Promise((res, rej) => res());
const secondPromise = new Promise((res, rej) => res());
const thirdPromise = new Promise((res, rej) => res());
// Remember, although this is a void funtion, it will
// still return a Promise.
async function theRightWay() {
const firstResult = await firstPromise;
const secondResult = await secondPromise;
const thirdResult = await thirdPromise;
console.log('Hello, World!');
}
theRightWay()
.then(() => {})
.catch(err => console.error(err));
Much more readable than before right?
If it’s possible that some of the promises that we are awaiting to throw an Error
, we should then wrap our code in try/catch
block.
const firstPromise = new Promise((res, rej) => res());
const secondPromise = new Promise((res, rej) => res());
const thirdPromise = new Promise((res, rej) => res());
async function theRightWay() {
try {
const firstResult = await firstPromise;
const secondResult = await secondPromise;
const thirdResult = await thirdPromise;
} catch(err) {
throw err; // If there is an error, our function's Promise will be rejected
} finally {
console.log('Hello, World!');
}
}
theRightWay()
.then(() => {})
.catch(err => console.error(err));
Use Default Parameters
In JavaScript, we don’t have an easy way to ensure the functions we write will receive the right arguments. So it’s not uncommon to see something like this.
function doubleNumber(number) {
if (number !== undefined) {
return number * 2;
}
return 0;
}
console.log(doubleNumber(2)) // 4
console.log(doubleNumber()) // 0
Don’t know about you but I hate writing functions this way. What if I told you there is an easier way to handle corner cases like these.
With modern JavaScript, you can provide a default value to parameters in the function declaration.
You can write the previous example this way:
function doubleNumber(number=0) {
return number * 2;
}
console.log(doubleNumber(2)) // 4
console.log(doubleNumber()) // 0
We are getting the same result but with less code. Now that’s an awesome feature.
Use The Right Tools For The Right Job
It’s no secret that JavaScript has the largest community of all programming languages. According to ModuleCounts there are 1070204 on NPM as of writing this article and today there have been around 835 new ones.
So it’s no surprise that JavaScript has some of the best tools that make the life of developer easier.
The ones I recommend you use:
- Webpack. Webpack is a module bundler. It takes your HTML, CSS and js files and optimizes them for max efficiency. It can also do a lot of more things like transpile your SCSS code in CSS, optimize your images, transpile your modern code to an older version of JavaScript so it can be compatible with older browsers.
- TypeScript. TypeScript is just JavaScript with extra features like static types, interfaces and decorators. You should definitely use it in bigger projects because the more code you have, the more debugging you will have to do. A lot of JavaScript bugs happen because of the lack of static types and TypeScript can help you reduce your debugging time.
- ESLint. ESLint is a static code analysis tool which makes your project more maintainable and more readable. If you don’t want to set it up and you are using VS Code, you can just install eslint plugin for it and it will give you suggestions on how to write better code. ESLint also prevents (some) JavaScript bugs before they even happen.
- Prettier.Prettier is opinionated code formatter. It forces you to write your code without thinking about what style guide you should follow. Unlike ESLintwhich formats only
.js
files, Prettier can format all kinds of files like.html
,.jsx
,.scss
,.vue
, etc. Sadly, Prettier can only detect bad formatting in your code but not bugs before you run your code like ESLint.
Of course you don’t need to use these exact tools. You can use for example CoffeeScript or Flow instead of TypeScript or Rollup or Parcel instead of WebPack.
Do your own research to find the best tools that fit both the project and your preferences.
Like What You're Reading?
Use debugger
Did you know JavaScript has debugger built in itself. You can invoke it by placing debugger
statement on the line where you want to pause the execution of code.
With that, you can now use Chrome DevTools or whatever DevTools you are using to see the current state of the whole application.
You can:
- Inspect variable values
- Go step by step through your code
- See the call stack of your functions
- Activate and deactivate more breakpoints
I could go on and on about debugger
statement but why you should listen to me when Google has an awesome article about it. You check it out here.
There is more to console
other than console.log
I don’t remember ever seeing someone using console
API other than using console.log
and it infuriates me.
To be fair, some methods from console
API are not something you need in everyday situations but still, there are a ton of you should be using RIGHT NOW. Here are a couple of those methods that I am using almost every day.
console.assert
console.assert
will take an assertion as it’s first argument and print an error message if that assertion is false. Simple as that but very useful.
console.time and console.timeEnd
Imagine having a really resource consuming function and you want to measure the time it needs to execute. Timers
in Console API
allow you to do that. Here is a quick example of how they work.
console.time(); // We start our timer
someComplexFunction(); // We call our block of code
console.timeEnd(); // We stop our default timer and print the result to console
Pretty cool, right? You can also label your timers so you can nest them.
console.time('totalTime');
console.time('complexFunctionTime');
someComplexFunction();
console.timeEnd('complexFunctionTime')
/*
* MORE CODE HERE
*/
console.timeEnd('totalTime');
Just remember you can have only 10,000 timers running simultaneously on one page.
console.error and console.warn
These are pretty straightforward. console.error
is used the same way as console.log
but will format it as an error. console.warn
is the same but will format our message as a warning.
console.table
console.table
is definitely the most mindblowing one. It allows us to log our objects in tabular format. Just imagine having an array with 100 objects in it and you have to log them out. It’s a nightmare to traverse. Luckily some very smart people came up with console.table
.
const users = [
{
"company": "Ullrich - Nader",
"username": "Lexus.Bailey3",
"password": "Qi2zpcDdTouyQHE",
"role": "user",
"phone": "(555) 823-2264",
"cell": "(972) 791-2009"
},
{
"company": "Hilll, Homenick and Wyman",
"username": "Enrico13",
"password": "XUymoxm95hDO_Vj",
"role": "user",
"phone": "(115) 225-2681",
"cell": "(214) 595-1386"
},
{
"company": "Parisian Inc",
"username": "Antwon.Koss",
"password": "xGhGjlRbV6YXaPm",
"role": "user",
"phone": "(180) 871-7340",
"cell": "(469) 245-6636"
}
];
console.table(users);
If you try running this example in the browser you will most probably see something like this:
In conclusion
I hope you have found these tips useful. I tried to include something for everyone from beginner to expert.
To recap what we have learnt:
- Never use
var
; Always useconst
orlet
. - Write your functions with arrow syntax where
function
keyword is not needed. - Use
===
; Forget that==
even exists. - Use
forEach
for iterating over the whole array andfor...of
if you don’t need to go over the whole array. - Teach yourself
async/await
syntax. It’s the future. - Provide default parameters to your functions.
- Find the tools that make your life easier.
debugger
can save you hours ofconsole.loging
. Make them count.- Remember that
console
has methods other thanlog
.
This is just the tip of the iceberg but I hope I have made the Web a little bit better by writing this.
Interested in learning more? Udacity has awesome course on front-end development. Check it out here.
Happy coding!