DEV Community

John Au-Yeung
John Au-Yeung

Posted on

JavaScript Best Practices for Writing Robust and Performant Code

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

Even more articles at http://thewebdev.info/

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are a lot of tricky parts to JavaScript, so there are things we should avoid that reduce the quality of our code. By following best practices, we can create elegant and manageable code that’s easy for anyone to work with.

In this article, we’ll look at how to gracefully support old browsers, avoiding heavy nesting, various optimizations, and checking the data in our programs.

Progressive Enhancement

Progress enhancement means that we should degrade our app gracefully when some technology isn’t available. This means that we should check if some technology is supported in all the browsers that we want to support and keep our app working in some way if it doesn’t.

We can also add polyfills to add support for new technologies that older browsers that we support don’t have.

For example, if we want to use new array methods that aren’t available in Internet Explorer 11 but our app still supports the browser, then we have to add a polyfill for it or check for its existence and do something different instead of crashing.

Avoid Heavy Nesting

Having lots of nesting in code makes them very confusing to read. This is because it’s very hard to follow the logic of the code.

Nesting conditional statements and loops should be kept to a minimum.

For example, instead of writing:

const items = {  
  foo: [1, 2, 3],  
  bar: [1, 2, 3],  
  baz: [1, 2, 3]  
};

const parentUl = document.createElement('ul');  
for (const item of Object.keys(items)) {  
  const parentLi = document.createElement('li');    
  const childUl = document.createElement('ul');  
  for (const num of items[item]){  
    const childLi = document.createElement('li');  
    childLi.textContent = num;  
    childUl.appendChild(childLi);      
  }  
  parentLi.textContent = item;  
  parentLi.appendChild(childUl);    
  parentUl.appendChild(parentLi);  
}  
document.body.appendChild(parentUl);
Enter fullscreen mode Exit fullscreen mode

This creates a nested list, which is confusing to read and write. We should reduce nesting by separating the list creating into a function and call the function instead:

const items = {  
  foo: [1, 2, 3],  
  bar: [1, 2, 3],  
  baz: [1, 2, 3]  
};

const createUl = (items) => {  
  const ul = document.createElement('ul');  
  for (const item of items) {  
    const li = document.createElement('li');  
    li.textContent = item;  
    ul.appendChild(li);  
  }  
  return ul;  
}

const parentUl = createUl(Object.keys(items));  
const parentLis = parentUl.querySelectorAll('li');  
for (const parentLi of parentLis) {  
  const childUl = createUl(items[parentLi.textContent]);  
  parentLi.appendChild(childUl);  
}  

document.body.appendChild(parentUl);
Enter fullscreen mode Exit fullscreen mode

As we can see, the code above doesn’t have any nested loops, which makes the code easier to read. Also, we have a createUl function to create the ul element with entries inside and returns the ui element object.

This means that we can attach it however we like to the document or an HTML element afterward.

Optimize Loops

We should cache values that are used in every literation in a single variable.

This is because every time we do this, the CPU has to access the item in memory again and again to compute its results.

Therefore, we should do this as little as possible.

For example, if we have a loop, we shouldn’t write the following:

for (let i = 0; i < arr.length; i++) {}
Enter fullscreen mode Exit fullscreen mode

Instead, we should write:

let length = arr.length;  
for (let i = 0; i < length; i++) {}
Enter fullscreen mode Exit fullscreen mode

This way, arr.length is only referenced once in our loop instead of accessing it in every iteration.

Keeping DOM Access to a Minimum

DOM manipulating is a CPU and memory-intensive operation. Therefore, we should strive to keep it to a minimum.

This means we have to keep pages as simple as possible and only do DOM manipulation when it’s necessary. Any static styles should be in CSS and not added on the fly with JavaScript.

Also, we should keep any static elements in HTML and not create them by manipulating the DOM.

Also, we should make functions that create elements and call them when we need to rather than continuously doing DOM manipulating operations on the top-level of the code.

Write Code for All Browsers

All browsers should get the same treatment by our code. We shouldn’t write hacks to accommodate various browsers because these hacks will be broken quickly when the browser changes versions.

We should stick to code that’s are accepted as standards or use libraries like Modernizr to deal with issues with different browsers.

Also, we can add polyfills to add any functionality that is missing in various browsers, so we can keep our app running on different browsers even though there’s they might not support some functionality out of the box.

Don’t Trust Any Data

We should check for any data that’s inputted by the user. HTML5 has lots of form validation functionality to check for valid inputs. We can do it with HTML5 and plain JavaScript.

Once we check for the inputted data, we also need to check for data in variables and returned from functions. Since JavaScript is a dynamically typed language, we’ve to check for these things.

For primitive values, we can use the typeof operator to check the data type of data. For example, if we have:

let x = 1;
Enter fullscreen mode Exit fullscreen mode

Then typeof x will return 'number'. Other primitive data types like boolean, strings, undefined, etc. are the same.

The only exception is null, which has the type object.

We should always check for values like null or undefined since they might crash our program. We can do that by writing x === null and typeof x === 'undefined' respectively.

Also, we should be careful of type coercions done by JavaScript, like in conditional statements and function calls. For example, the Math.min method converts its arguments to numbers when it’s called. The == operator converts all the operands to the same type before returning the result.

For objects, we can check for their type by using the instanceof operator to see which constructor they’re created from. For example, if we have an array:

let arr = [];
Enter fullscreen mode Exit fullscreen mode

Then [] instanceof Array will be true. Arrays also have a static isArray method to check for the data type.

In the JavaScript code, we write, we should be aware of the differences in different browsers that our app supports. This means checking if the methods that we want to use exist and adding polyfills to add missing functionality for older browsers.

We should cache variables and properties that are accessed repeatedly in loops so they don’t have to be accessed each time the loop runs.

Deep nesting should also be avoided to keep code clear and easy to follow.

Also, since DOM manipulation is an expensive operation, it should be kept to a minimum. Static styles and elements should be in CSS and HTML respectively.

Finally, we shouldn’t trust the data in our apps. Inputs have to be checked for format and validity, and data in our variables and values should be checked for type, including null or undefined.

Top comments (1)

Collapse
 
aumayeung profile image
John Au-Yeung

That's a good find. I just make sure to have as little nesting and branches as possible. If I think it's too hard to read then it's probably too hard.