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);
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);
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++) {
}
Instead, we should write:
let length = arr.length;
for (let i = 0; i < length; i++) {
}
This way, arr.length
is only referenced once in our loop instead of accessing it in every iteration.
Photo by Adrien CÉSARD on Unsplash
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;
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 = [];
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
.