DEV Community

Melvinvmegen
Melvinvmegen

Posted on • Edited on • Originally published at blog.melvinvmegen.com

14 tips to write better Javascript

Image description

Here are some of my favorite tips for writing cleaner Javascript code, skip the clever keep it simple.

1. Forget about var

Declare all your variables with either const or let. As a rule of thumb, use const by default otherwise if a variable needs to be reassigned use let.

The var keyword should be avoided as it is almost scope free leading to potential scoping bugs, refer to my guide on hoisting.

Bonus: it’s best practice to initialize your variables at the time of creation so you and your team can ensure none are left unused.

// ❌ Avoid this
var old = "";

// ✅ Do this
const immutable = "John";
let counter = 1;
counter++; // counter === 2;

// Declare objects and arrays as const to prevent type change
const user = {firstname: "John", lastname: "Doe"};
const users = ["Mac", "Roe"];
Enter fullscreen mode Exit fullscreen mode

2. Be strict about equality

The strict equality operator (===) same as the equality operator checks whether its two operands are equal, returning a Boolean result. But unlike the equality operator (==), the strict equality operator (===) always considers operands of different types to be different.

Bonus: 0 being falsy will be wrongly equal to false in case of non strict equality operator.

// ❌ Avoid this
1 == "1"; // true
0 == false; // true

// ✅ Do this
1 === 1; // true
1 === "1"; // false
0 === false; // false
Enter fullscreen mode Exit fullscreen mode

3. Avoid constructors for primitive values

Primitive objects are strictly different than their primitive counterparts making
them harder to check for strict equality as they're wrapped in a object.

They're are basically equivalent but not equal.

// ❌ Avoid this
const stringObject = new String("Charly");

// ✅ Do this
const stringPrimitive = "Charly";

// Equality check
stringPrimitive === stringObject; // false 
new Number(1) === 1; // false
new Boolean(true) === true; // false
Enter fullscreen mode Exit fullscreen mode

4. Make use of Object Literals

Object literals are a shorthand notation allowing you to define an object or an array on the fly.

Thereby avoiding repetition, improving readability and preventing mistakes as we can't infer logic behind it, are we currently initializing a variable or updating it?

// ❌ Avoid this
const user = new Object(); // {}
user.firstname = "John"; // { firstname: "John" }
user.lastname = "Doe"; // { firstname: "John", lastname: "Doe" }

// ✅ Do this
const user = { 
  firstname: "John", 
  lastname: "Doe" 
};

// ❌ Avoid this
const fruits = new Array(); // []
fruits.push("banana"); // ["banana"]
fruits.push("mango"); // ["banana", "mango"]

// ✅ Do this
const fruits = ["banana", "mango"];
Enter fullscreen mode Exit fullscreen mode

5. Use template literals to combine strings

Putting strings together is a pain, especially when combining strings and variables.

To make this process simpler, you can use template literals (marked by backticks), which take both strings and variables as long as it is surrounded by the interpolation (${}).

const firstname = "John";

// ❌ Avoid this
let greeting = "Hello, " + firstname; // Hello, John

// ✅ Do this
greeting = `Hello, ${firstname}`; // Hello, John
Enter fullscreen mode Exit fullscreen mode

6. Use Semicolons for line termination

The use of semi-colons for line termination is always a good practice.

You won’t be warned if you forget it, because in most cases it will be inserted by the JavaScript parser. But without it how would you know when an expression ends?

Take the for loop as an example :

// ✅ Do this
for (let i = 0; i < numbers.length; i++) {
  console.log(numbers[i]);
}
Enter fullscreen mode Exit fullscreen mode

You wouldn't be able to do the following because to parser thinks it is one expression while it truly is three separate ones:

// ❌ Not this
for (let i = 0 i < numbers.length i++) {
  console.log(numbers[i]);
} // Uncaught SyntaxError: Unexpected identifier
Enter fullscreen mode Exit fullscreen mode

7. Use object param instead of multiple params

I consider it a code smell to define too many parameters in my functions. Even if parameters have default value or are optional, let's take this example:

// ❌ Avoid this
function avatarUrl(avatar, format = "small", caption = true) {
  // Does something
}

avatarUrl(user.avatar, 'thumb', false)
Enter fullscreen mode Exit fullscreen mode

When you use this function it's very hard to know which parameters are used and how. What does the last param false, stand for here?

No idea, we have to open the function definition in order to know.
And what happens if you need to change the parameter's order? Well you'll have to change all function calls.

With an object, order doesn't matter:

// ✅ Do this
function avatarUrl(avatar, options={format: 'small', caption: true}) {
  // Does something
}

avatarUrl(user.avatar, {
  format: "thumb", 
  caption: false
})
Enter fullscreen mode Exit fullscreen mode

8. Return as soon as possible

Nested conditions make it hard to understand code but you can easily avoid it with guard clauses, by returning early.

Guard clauses will allow you to remove most of your else conditions making your code readable like plain English.

// ❌ Avoid this
function doSomething() {
  if (user) {
    if (user.role === "ADMIN") {
      return 'Administrator';
    } else {
      return 'User';
    }
  } else {
    return 'Anonymous';
  }
}

// ✅ Do this
function doSomething() {
  if (!user) return 'Anonymous'
  if (user.role === "ADMIN") return 'Administrator'

  return 'User'
}
Enter fullscreen mode Exit fullscreen mode

9. Learn and use the power of your tools

Javascript provides a lot built-in function on Array, Object, String.

Find and learn them to enjoy the full power of your stack.

// ❌ Avoid this
const users = [
  {
    username: "JohnDoe",
    admin: false
  },
  {
    username: "Todd",
    admin: true
  },
];
const admins = [];

function getAdmins() {
  users.forEach((user) => {
    if (user.admin) admins.push(user)
  })

  return admins
}

// ✅ Do this
function getAdmins() {
  return users.filter((user) => user.admin)
}
Enter fullscreen mode Exit fullscreen mode

10. Code for humans, not for computers

Let's assume it, most of us are bad at noticing differences, it might take us a few seconds before noticing a logical not (!).

Let's take this example:

const users = [
  {
    username: "JohnDoe",
    admin: false
    enabled: true
  },
  {
    username: "Todd",
    admin: true
    enabled: true
  },
];

// ❌ Avoid this
const members = users.filter(u => u.enabled).map(u => !u.admin)
const admins = users.filter(u => u.enabled).map(u => u.admin)

// ✅ Do this
const enabledUsers = users.filter(u => u.enabled)
const members = enabledUsers.map(u => !u.admin)
const admins = enabledUsers.map(u => u.admin)
Enter fullscreen mode Exit fullscreen mode

Both members and admins assignments only differ by the logical not (!) and if you need to change one assignment, then you also need to change the other one.

Another example, do not use magic numbers. Use explicit variables instead:

// ❌ Avoid this
function price_with_taxes(price) {
  return price * 1.2
}

// ✅ Do this
const taxRate = 1.2
function price_with_taxes(price) {
  return price * taxRate
}
Enter fullscreen mode Exit fullscreen mode

11. Avoid abbreviations

Whether you write e for event, t for ticket won't boost your productivity but it does worsen readibility and decreases instant comprehension.

// ❌ Avoid this
function someFunction() {
  events.forEach(e => {
    e.tickets.forEach(t => {
      `${e.name} for ${t.full_name}`
    })
  })
}

// ✅ Do this
function someFunction() {
  events.forEach(event => {
    event.tickets.forEach(ticket => {
      `${event.name} for ${ticket.full_name}`
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

Here you don't have to guess what e and t stand for, you just read it.
Coding is complex enough to bloat your mind with extra complexities. This also applies to variables, class, methods...

There are few exceptions though, it's ok to use widely used abbreviations like i in your for-loops.

12. Avoid useless && negated conditions

Conditions are like memos for your brain as you need to remember them while stepping through each line of code in order to understand what's going on.

Fortunately, most of them can be simplified thanks to my favorite ES6 operator optional chaining.

// ❌ Avoid this
function doSomething(params) {
  if (params && params.filter) return 'Foo'

  return 'Bar'
}

// ✅ Do this
function doSomething(params) {
  if (params?.filter) return 'Foo'

  return 'Bar'
}
Enter fullscreen mode Exit fullscreen mode

I don't know about you but every time i see a logical not (!) it makes my brain pause for a second, to me it feels more natural to read something like:

if a user IS an admin then we do that

rather than:

if a user IS NOT an admin then we do that.

// ❌ Avoid this
function doSomething(user) {
  if (!user || !user.admin) {
    // Case where no user or not admin
  } else {
    // Case where user and user is admin
  }
}

// ✅ Do this
function doSomething(user) {
  if (user && user.admin) {
    // Case where user and user is admin
  } else {
    // Case where no user or not admin
  }
}
Enter fullscreen mode Exit fullscreen mode

13. Use for...of instead of for loops

Using the for...of statement instead of the classical for loop is such a JavaScript improvement.

This syntax has been introduced by ES6, and it includes a built-in iterator so that you don’t have to define your own variable, increment it until a certain length value:

let users = ["Fedor Emelianenko", "Cyril Gane", "Conor McGregor"];

// ❌ Avoid this
// This avoids length behind reavaluated at every iteration
let usersCount = users.length;
for (let i = 0; i < usersCount; i++) {
    console.log(users[i]);
}

// ✅ Do this
for(let user of users) {
  console.log(user);
}
Enter fullscreen mode Exit fullscreen mode

Notice how much more readable it is! And you don't have to care about all this (let i = 0; i < usersCount; i++) clumsy logic though you might need it for some specific use case, like an irregular interval.

14. Readable code outshines clever code

Always remember, you're writing code with other developers aswell as your future self. You don't want to create more problems than the ones you're solving writing code.

Don't write code to show off your skills, write code everyone can understand and debug.

If you have more tips, i'd be happy to learn them from you in the comments!

Keep it simple!

Top comments (33)

Collapse
 
ademagic profile image
Miko • Edited

All good advice but I think a lot of these are standard ways of working in modern JS. I looked back at some of these and forgot you could do it the wrong way. JS has come a long way in that respect.

But a timeless and massively underrated tip

14. Readable code outshines clever code

Every language. Every time.
Thanks for the article!

Collapse
 
melvinvmegen profile image
Melvinvmegen

Yeah, you're right! But not all JS code has been written after ES6, unfortunately, and on a day-to-day basis, I still encounter a lot of legacy code that deserves to be refactored. I personally use those rules as a reminder :)

Collapse
 
ademagic profile image
Miko

Great point, I only thought about this as how to write new JS, I didn't even consider this article from the refactor perspective. Hopefully a lot of this gets picked up by linters, its always nice when automation helps you write better code.

Thread Thread
 
melvinvmegen profile image
Melvinvmegen

That's true nowadays a well configured linter can catch a lot of it! But still good to know why it matters

Collapse
 
codingjlu profile image
codingjlu

For number 6 the semicolon isn't used for line termination—it's the standard for-loop syntax. Aside from that, generally ASI works perfectly, and if you know its quirks there is nothing wrong with omitting semicolons and can make your code a lot cleaner.

Collapse
 
melvinvmegen profile image
Melvinvmegen

Well if there is a need for ASI it does mean that it's used for line termination doesn't it?
I believe it is a matter of personal preference but to me it just makes sense to be explicit about where the command should end.

Collapse
 
devncoffee profile image
Dev N Coffee • Edited

@melvinvmegen DUDE, DON'T DITCH VAR!

It is very misleading to beginners, and they will never grasp the concept of scope properly.

Otherwise, I will agree with many of the above points, and add on with a couple of the modern operators:

  • Arrow functions
  • Spread
  • Ternary operator
// ARROW FUNCTION
var fn = (name, email) => {
  console.log(name, email);
};

// DUMMY DATA
var person = ["Jon", "jon@doe.com"];

// SPREAD
fn(...person);

// ARROW FUNCTIONS CAN BE "MINIFIED" IF ONLY 1 PARAM AND/OR SINGLE LINE
var double = (a) => { return a *2; };
var double = a => a *2;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
melvinvmegen profile image
Melvinvmegen

Thanks for your additional tips it may help others!

Honestly i think that it's even more misleading when you're learning about scopes that some variables are acting differently than others..

Collapse
 
amiceli profile image
amiceli

I love the 7 rules <3, it makes code easier to read.
I use it since I read Clean Code book.
But the 6 rule ^^ No semicolons for me !

Good posts ;)

Collapse
 
welshapple profile image
William • Edited

Agreed really good post but the part on semicolons is quite funny because half these examples don't even have semicolon line terminations in them.

Should be a matter of preference if you have semicolons at the end of your lines in JS. Personally I don't use them as I think it looks cleaner.

Collapse
 
melvinvmegen profile image
Melvinvmegen • Edited

Thank you! Haha yes indeed even most my examples don't include them, i must say prettier takes care of that for me most of the time.

But indeed it's a matter of personal preference, until you need it!

Collapse
 
melvinvmegen profile image
Melvinvmegen

Thank you for kind words! I feel like semicolons are (or used to be) a big debate in the js world but it as almost no impact so we should probably leave as a matter of personal preference. :)

Collapse
 
carloshdevbr profile image
KaleoSix

One strategy that I like to use is not necessarily in js but in ts, which would be to pass the parameters as typed objects, making TS tell me exactly what it expects to receive in my functions, not only the type it expects to receive, for me I feel that it is much more readable and easier to understand what the function wants to receive

Here's an example

`interface CreateFormDataProps {
key: string;
file: File;
fileName: string;
}

export const CreateFormData = ({ key = 'file', file, fileName }: CreateFormDataProps): FormData => {
const formData = new FormData();

formData.append(key, file, fileName);

return formData;
};
`

Collapse
 
melvinvmegen profile image
Melvinvmegen • Edited

You're right TS is a gem for thoses cases! I could have added TS has a rule but i wanted to focus on JS and i also believe that the bloat that it brings is not always valuable depending on the project size and "importance".

I recently tried jsDoc and i must say it brings even more bloat (as the syntax is not optimal) but i don't have the feeling that i'm writing twice as much code with additional complexity. It just brings types safety thanks to the editor and i'm not forced to use any whenever i'm stuck with a pattern that only a TS wizard would type correctly.

But again maybe it's just that i'm not qualified enough in TS.

Collapse
 
tusharshahi profile image
TusharShahi

Nice post.

A tip for the 3rd rule:
Use const stringObject = String("Charly"); instead and it would create a primitive instead of an object.

Collapse
 
melvinvmegen profile image
Melvinvmegen

Oh that's interesting i didn't know that! But i have to say i'd still use the classic const string = "Charly"

Collapse
 
jackfan profile image
Jack Fan

I think a 'let' should be added in the thirteenth suggestion.

// ✅ Do this
for(let user of users) {
console.log(users);
}

Collapse
 
madeofclay profile image
Adam

Oh man! I thought you were going to catch it too. @melvinvmegen, shouldn't the console log be logging user instead of users? You'll log the entire array the number of times of the array's length 😅

// ✅ Do this
for(let user of users) {
  console.log(user);
}
Enter fullscreen mode Exit fullscreen mode

What was it you said about easily missing the ! operator? 😁 single characters are easy to miss.

Collapse
 
melvinvmegen profile image
Melvinvmegen

Haha good catch! Failing my own rules 😕

Collapse
 
melvinvmegen profile image
Melvinvmegen • Edited

You're actually right! It's technically not an error because it works same if you do something like this :
`
test = 1;

// You can even reassign it
test = 2;

test // 2
`

But it might be var by default instead of let so either way best to specify it!

Collapse
 
merndev1101 profile image
Star

Thank you.

Collapse
 
melvinvmegen profile image
Melvinvmegen • Edited

My pleasure hope it helped!

Collapse
 
michaelcoder12 profile image
Michaelcoder12

Great post

Collapse
 
melvinvmegen profile image
Melvinvmegen

Thank you!

Collapse
 
_spurrt profile image
fuckmouth

do not use the short-if syntax, it's not considered good practice. DO use guard clauses, but do NOT use short-if syntax

Collapse
 
melvinvmegen profile image
Melvinvmegen

I like concise code but you're right it's technically not a good practice since the code block is inferred rather than explicit.

Some comments have been hidden by the post's author - find out more