Write better code by following these JavaScript best practices
Tags: javascript, webdev, programming, beginners
Published: 2023-05-18 09:50:32

Whether you're a seasoned developer looking for ways to refine your coding style or a beginner eager to grasp the essentials, this blog post is for you. In this comprehensive guide, we'll explore various best practices that can help you elevate your JavaScript skills to the next level.

## **1. Adopt a Consistent Coding Style**

The first step to improving your JavaScript game is to adopt a consistent coding style. Why is this important? A consistent coding style enhances the readability and maintainability of your code. Think of it as a blueprint that provides structure and coherence to your scripts.

There are several popular coding style guides that you can follow:

- [Idiomatic JavaScript](https://github.com/rwaldron/idiomatic.js/), which is community-based
- [Google's JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html)
- [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript)

Choose a guide that aligns with your programming style and stick to it throughout your projects, your projects should look like that only one person wrote the code even if many other developers worked on the same project.

## **2. Naming variables and functions**

Next on our list is the naming convention for variables, functions, and other code structures. This practice isn't just about aesthetics or semantics, but it's also about code readability and efficient debugging.

Remember, in JavaScript, the standard is to use camel-case for variables and functions (like **`myVariableName`**), and Pascal case for classes (like **`MyClassName`**).

```jsx
// ❌ Poorly named variables:
let a = 'John';
let fn = () => console.log('Hello');

// ✅ Descriptive variable names:
let firstName = 'John';
let sayHello = () => console.log('Hello');
```

## **3. Use Shorthands but Be Cautious**

Shorthands are a great way to write code faster and cleaner, but be wary as they might return surprising results. To prevent such unexpected outcomes, make sure to always check the documentation, find relevant JavaScript code examples, and test the result.

```jsx
// ❌ Traditional function declaration:
function square1 (num) {
  return num * num
}
// ✅ Using arrow functions (shorthand):
const square2 = num => num * num

// ❌ Very long code:
let x

if (y) {
  x = y
} else {
  x = 'default'
}

// ✅ A more succinct way to achieve the same result using logical OR:
let x = y || 'default'
```

## **4. Add meaningful comments to your code**

Commenting on your code is like leaving breadcrumbs for your future self or other developers. It helps in understanding the flow and purpose of your code, especially when working on team projects. 

However, remember to keep your comments short, and concise, and only include key information.

```jsx
// ❌ Over-commenting or not commenting at all:
function convertCurrency (from, to) {
  // Conversion logic goes here
}

// ✅ Using appropriate comments:
/**
 * Converts currency from one type to another
 *
 * @description Converts one currency to another
 * @param {string} from - The currency to convert from
 * @param {string} to - The currency to convert to
 * @returns {number} - The converted amount
 * */
function convertCurrency (from, to) {
  // Conversion logic goes here
}
```

In this example, the first one does not give the developer what is `from` and what is `to` , but the second example lets the programmer easily understand what are the arguments for and describes what the function does.

## **5. Follow the SoC principle**

To keep things simple and organized, it's best not to use JavaScript for adding direct styling. This is known as separation of concerns (SoC). Instead, use the `classList` API to add and remove classes, and define the style rules with CSS. 

This way, CSS does all the styling and JavaScript handles all of the other functionalities of your application.

This programming concept is not exclusive to JavaScript, (SoC) the Separation of concerns is a practice to separate functionalities and not mix up different technologies.

To keep it short: CSS should do CSS stuff but JavaScript should not do CSS.

```jsx
// ❌ Avoid using JavaScript for styling:
let element = document.getElementById('my-element')
element.style.color = 'red'

// ✅ Changing styles by adding/removing classes:
let element = document.getElementById('my-element')
element.classList.add('my-class')
```

## **6. Avoid global variables**

When declaring local variables, use **`let`** and **`const`** to prevent overriding global variables. Both create block-scoped local variables, but the key difference is that **`let`** can be re-declared while **`const`** can't. So, use them wisely according to your specific needs.

```jsx
// ❌ Using var for variable declaration:
var x = 1

// ✅ Using let and const for variable declaration:
let x = 1
const y = 2
```

## **7. Use `for...of` Instead of `for` Loops**

The **`for...of`** the statement, introduced in ECMAScript 6, is a more efficient alternative to traditional **`for`** loops. It comes with a built-in iterator, eliminating the need to define the variable and the length value. This makes your code cleaner and enhances its performance.

```jsx
// ❌ Using traditional for loop:
let cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix']
for (let i = 0; i < cities.length; i++) {
  const city = cities[i]
  console.log(city)
}

// ✅ Using for of loop:
let cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix']
for (const city of cities) {
  console.log(city)
}
```

## **8. Adhere to the Single responsibility principle**

One way to adhere to the single responsibility principle is by creating helper functions for common tasks.

These functions should be context-independent so they can be called from any module, enhancing the reusability and modularity of your code.

```jsx
// ❌ Doing multiple tasks in one function:
function calculateOrderTotal (order) {
  let total = 0

  for (let i = 0; i < order.items.length; i++) {
    total += order.items[i].price * order.items[i].quantity
  }

  let tax = total * 0.07

  return total + tax
}

// ✅ Doing one task in one function:
function calculateSubTotal (items) {
  let total = 0

  for (let i = 0; i < items.length; i++) {
    total += items[i].price * items[i].quantity
  }

  return total
}

function calculateTax (total) {
  return total * 0.07
}

function calculateOrderTotal (order) {
  let subTotal = calculateSubTotal(order.items)
  let tax = calculateTax(subTotal)

  return subTotal + tax
}
```

## **9. Understand the Lack of Hoisting in Classes**

Unlike functions, classes in JavaScript are not hoisted, meaning you need to declare a class before you call it. This might seem counterintuitive at first, especially if you're accustomed to function hoisting, but it's a fundamental principle that must be understood and respected when using classes in JavaScript.

```jsx
// ❌ Calling a class before declaration:
const hat = new Hat('Red', 1000)
hat.show()
class Hat {
  constructor (color, price) {
    this.color = color
    this.price = price
  }
  show () {
    console.log(`This ${this.color} hat costs $${this.price}`)
  }
}

// ✅ Calling a class after declaration:
class Hat {
  constructor (color, price) {
    this.color = color
    this.price = price
  }
  show () {
    console.log(`This ${this.color} hat costs $${this.price}`)
  }
}

const hat = new Hat('Red', 1000)
```

## **10. Avoid Mutating Function Arguments**

Directly modifying the properties of an object or the values of an array passed as a function argument can lead to undesirable side effects and hard-to-trace bugs. Instead, consider returning a new object or array. This practice aligns well with the principles of functional programming where immutability is key.

```jsx
// ❌ Mutating function arguments:
function updateName (user) {
  user.name = 'bob'
}

let user = { name: 'alice' }
updateName(user)
console.log(user) // { name: 'bob' }

// ✅ Avoid mutating function arguments, return new object instead:
function updateName (user) {
  return { ...user, name: 'bob' }
}

let user = { name: 'alice' }
let updatedUser = updateName(user)
console.log(user) // { name: 'alice' }
console.log(updatedUser) // { name: 'bob' }
```

## **11. Handle Promises Correctly**

In the asynchronous world of JavaScript, promises are a prevalent concept. However, they must be handled correctly to prevent unexpected behaviour. JavaScript provides a **`try...catch`** block that can be used to handle exceptions that may arise during the execution of your asynchronous code.

This way, you ensure errors are caught and dealt with appropriately, maintaining the robustness of your code.

```jsx
// ❌ Not handling promises correctly:
async function fetchData () {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
  const data = await response.json()

  return data
}

// ✅ Handling promises correctly:
async function fetchData () {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
    const data = await response.json()

    return data
  } catch (error) {
    // Handle your errors here...
    throw new Error(error)
  }
}
```

## 12. Avoid the **Over-Nesting in your code**

This one is one of the most common mistakes that beginners do, they nest block after block after block, a `for loop` inside another loop inside an `if.else` inside a `try catch` on and on.

Once you know, you have quite a clutter and you don’t know what exactly the code does and where to find the code responsible for what you’re looking for.

Debugging this type of code could be quite difficult and overwhelming, and when other programmers see it, you will confuse them too, not to mention you’ll give off the “Unprofessional” vibe.

```jsx
// ❌ Nesting code blocks too much and not using the return keyword
function checkNumber (num) {
  if (num > 0) {
    console.log('Number is positive.')
  } else {
    if (num < 0) {
      console.log('Number is negative.')
    } else {
      console.log('Number is zero.')
    }
  }
}

// ✅ Using the return keyword instead of the else statement
function checkNumber (num) {
  if (num > 0) {
    console.log('Number is positive.')
    return
  }

  if (num < 0) {
    console.log('Number is negative.')
    return
  }

  console.log('Number is zero.')
}
```

## 13. Optimizing for loops

There is a common way of writing for loops in JavaScript, here is an example of how most developers write loops in JavaScript

```jsx
var languages = ['Python', 'JavaScript', 'C++', 'Ruby'];
for(var i=0;i<languages.length;i++){
  codeIn(languages[i]);
}
```

For a small array like this, this loop is highly efficient and runs without any problem, but when it comes to larger datasets and an array with thousands of indexes, this approach can slow down your loop process.

What happens is that when you run `languages.length` the length of the array will be re-computed each time the loop runs, and the longer your array is, the more inefficient it will be to re-calculate the array length every time the loop runs especially when the length of the array is static which is in most cases.

What you should do instead is store the array length in a different variable and use that variable in your loops, for example:

```jsx
var languages = ['Python', 'JavaScript', 'C++', 'Ruby'];
var langCount = languages.length;
for(var i=0;i<langCount;i++){
  codeIn(languages[i]);
}
```

With this tweak, the 'length' property is accessed only once, and we use the stored value for subsequent iterations. This improves performance, particularly when dealing with large data sets.

Another elegant approach is to declare a second variable in the pre-loop statement:

```jsx
var languages = ['Python', 'JavaScript', 'C++', 'Ruby'];
for(var i=0, n=languages.length; i<n; i++){
  codeIn(languages[i]);
}
```

In this approach, the second variable 'n' captures the array's length in the pre-loop statement, thus achieving the same result more succinctly.

## Conclusion

Remember, these practices aren't just about writing cleaner and better-structured code. They're also about producing code that's easier to maintain and debug. 

Software development is a continuous process. Creating the development code is just the first step of the software life cycle. It's crucial to continually monitor, debug, and improve your code in the production stage.

So there you have it, 13 JavaScript best practices to elevate your coding game. Whether you're a beginner or an experienced developer, adopting these habits will help you write better, more efficient, and maintainable code. Happy coding!