Table of contents
In a previous article, I covered front-end best practices in CSS/SCSS. This article will share the basic javascript best practices for development
Frontend development in JavaScript involves creating user interfaces and handling the presentation layer of web applications. Here are some best practices to follow, along with examples, to ensure a clean and maintainable codebase:
1.Modularization: Break down your code into smaller, reusable modules. This enhances code readability and makes it easier to manage dependencies.
Example:
// users.js (module)
export function getUsers() {
// Fetch users from the API or data source
}
// main.js (entry point)
import { getUsers } from './users.js';
getUsers();
2.Use const and let: Prefer const
for variables that won't be reassigned and let
for variables that will change.
Example:
const PI = 3.14159;
let count = 0;
count = 10; // Valid
PI = 3; // Error
3.Avoid global variables: Minimize the use of global variables to prevent polluting the global scope and potential conflicts.
Example:
// Avoid this
let globalVar = 'I am global';
function someFunction() {
// ...
}
// Use this instead
(function() {
let localVar = 'I am local';
function someFunction() {
// ...
}
})();
4.Use arrow functions: Arrow functions provide concise syntax and maintain the lexical this
value, reducing the need for bind()
.
Example:
// Regular function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => a + b;
5.Avoid polluting the global namespace: Encapsulate your code within a module or an IIFE (Immediately Invoked Function Expression) to avoid global namespace pollution.
Example:
// Instead of
function myFunction() {
// ...
}
// Use this
(function() {
function myFunction() {
// ...
}
// Call the function or attach it to the desired scope
myFunction();
})();
6.Use modern ES6+ features: Embrace ES6+ features like destructuring, spread syntax, and template literals to write more concise and expressive code.
Example:
// Destructuring
const { firstName, lastName } = user;
// Spread syntax
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArray = [...arr1, ...arr2];
// Template literals
const name = `My name is ${firstName} ${lastName}.`;
7.Avoid inline styles and use CSS classes: Keep your HTML and JavaScript code separated. Use CSS classes for styling, and manipulate classes with JavaScript instead of inline styles.
Example:
<!-- Bad: Inline style -->
<button style="background-color: #007bff; color: #fff;">Click Me</button>
<!-- Good: CSS class -->
<button class="primary-btn">Click Me</button>
8.Optimize DOM manipulation: Minimize direct DOM manipulation and use efficient approaches like template literals or libraries/frameworks that efficiently update the DOM.
Example (using template literals):
const data = ['Item 1', 'Item 2', 'Item 3'];
function renderList(data) {
const list = document.getElementById('list');
list.innerHTML = '';
data.forEach(item => {
const listItem = document.createElement('li');
listItem.textContent = item;
list.appendChild(listItem);
});
}
renderList(data);
9.Use event delegation: Attach event listeners to parent elements and utilize event delegation for handling events on dynamically added elements.
Example:
<ul id="list">
<!-- List items will be added dynamically -->
</ul>
document.getElementById('list').addEventListener('click', event => {
if (event.target.nodeName === 'LI') {
// Handle click on list item
console.log(event.target.textContent);
}
});
10.Optimize asset loading: Minimize the number of HTTP requests and use techniques like bundling and minification to optimize asset loading.
11.Error Handling: Always handle errors gracefully to avoid unexpected application crashes and improve user experience.
Example:
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero is not allowed.');
}
return a / b;
}
try {
const result = divide(10, 0);
console.log(result);
} catch (error) {
console.error('An error occurred:', error.message);
}
12.Use Promises or Async/Await for Asynchronous Operations: Avoid using nested callbacks for asynchronous operations and use Promises or Async/Await to improve code readability and maintainability.
Example using Promises:
function fetchData() {
return fetch('https://api.example.com/data')
.then(response => response.json());
}
fetchData()
.then(data => console.log(data))
.catch(error => console.error('Error fetching data:', error));
Example using Async/Await:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
throw new Error('Error fetching data:', error);
}
}
(async () => {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error.message);
}
})();
13.Avoid Manipulating the DOM Directly in Loops: When performing DOM manipulation inside loops, batch the changes or use DocumentFragment to minimize layout thrashing and improve performance.
Example:
// Bad: Directly manipulating DOM in a loop
const list = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
const listItem = document.createElement('li');
listItem.textContent = `Item ${i}`;
list.appendChild(listItem);
}
// Good: Batch changes using DocumentFragment
const list = document.getElementById('list');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const listItem = document.createElement('li');
listItem.textContent = `Item ${i}`;
fragment.appendChild(listItem);
}
list.appendChild(fragment);
14.Use Debouncing or Throttling for Event Handlers: When handling events that could trigger frequently (e.g., resize or scroll), use debouncing or throttling techniques to reduce the number of function calls and improve performance.
Example using Lodash's debounce
function:
import { debounce } from 'lodash';
function handleResize() {
// Code to handle window resize
}
window.addEventListener('resize', debounce(handleResize, 200));
15.Use Semantic HTML: Write meaningful and semantic HTML to improve accessibility, SEO, and maintainability.
Example:
<!-- Bad: Using generic divs -->
<div class="header">
<div class="title">My Website</div>
</div>
<!-- Good: Using semantic HTML -->
<header>
<h1>My Website</h1>
</header>
16.Use ES6 Modules instead of Global Scripts: Organize your JavaScript code into separate modules, and use ES6 import
and export
statements instead of loading multiple scripts in the global scope.
Example (Module 1):
// module1.js
export function foo() {
// ...
}
Example (Module 2):
// module2.js
export function bar() {
// ...
}
Example (Main Script):
// main.js
import { foo } from './module1.js';
import { bar } from './module2.js';
foo();
bar();
17.Avoid Nested Ternary Operators: While ternary operators can be useful for concise expressions, nesting them can lead to code that is hard to read and understand. Instead, use regular if-else statements for complex conditions.
Example:
// Bad: Nested ternary
const result = condition1
? value1
: condition2
? value2
: condition3
? value3
: defaultValue;
// Good: Using if-else
let result;
if (condition1) {
result = value1;
} else if (condition2) {
result = value2;
} else if (condition3) {
result = value3;
} else {
result = defaultValue;
}
18.Avoid Excessive Comments: Comments are essential for code documentation, but avoid over-commenting self-explanatory code. Let the code speak for itself whenever possible.
Example:
// Bad: Excessive comments
function add(a, b) {
// This function adds two numbers and returns the result
return a + b; // Return the sum
}
// Good: Minimal, self-explanatory comments
function add(a, b) {
return a + b;
}
19.Use Object Shorthand: When creating object literals with properties that have the same name as the variables, use object shorthand for cleaner code.
Example:
// Bad: Repetitive code
const firstName = 'John';
const lastName = 'Doe';
const user = {
firstName: firstName,
lastName: lastName,
};
// Good: Object shorthand
const firstName = 'John';
const lastName = 'Doe';
const user = {
firstName,
lastName,
};
20.Avoid Using eval()
: The eval()
function can execute arbitrary code and is generally considered unsafe and bad practice. Find alternative solutions to achieve your goals without using eval()
.
Example (Bad - Avoid eval()
):
const expression = '10 + 20';
const result = eval(expression);
console.log(result); // Output: 30
21.Use textContent
Instead of innerHTML
: When working with plain text content, prefer textContent
over innerHTML
to prevent potential security vulnerabilities (e.g., cross-site scripting - XSS).
Example:
// Bad: Using innerHTML for plain text
const text = '<script>alert("Hello XSS!");</script>';
const element = document.getElementById('myElement');
element.innerHTML = text; // This will execute the script
// Good: Using textContent
const text = '<script>alert("Hello XSS!");</script>';
const element = document.getElementById('myElement');
element.textContent = text; // Treats it as plain text, no script execution
22.Use addEventListener
Instead of Inline Event Handlers: Instead of using inline event handlers in HTML (e.g., onclick="myFunction()"
), use addEventListener
in JavaScript for better separation of concerns.
Example (Bad - Inline Event Handler):
<button onclick="handleClick()">Click Me</button>
function handleClick() {
// Event handling logic
}
Example (Good - addEventListener
):
<button id="myButton">Click Me</button>
document.getElementById('myButton').addEventListener('click', handleClick);
function handleClick() {
// Event handling logic
}
23.Use const
and let
instead of var
: Prefer const
and let
over var
for variable declarations to avoid hoisting and block-scoping issues.
Example:
// Bad: Using var
var x = 10;
// Good: Using const or let
const x = 10;
let y = 20;
24.Use map()
, filter()
, and reduce()
for Array Operations: Utilize higher-order array methods like map()
, filter()
, and reduce()
to perform operations on arrays in a functional and declarative manner.
Example using map()
:
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(num => num * 2);
console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
Example using filter()
:
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // Output: [2, 4]
Example using reduce()
:
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // Output: 15
25.Avoid document.write()
: The use of document.write()
can cause unexpected behavior and overwrite the entire document if used after the page has finished loading. Use DOM manipulation methods instead.
Example (Bad - Avoid document.write()
):
document.write('<h1>Hello World</h1>');
26.Use classList
for Managing CSS Classes: Instead of directly manipulating the className
, use classList
methods like add()
, remove()
, toggle()
, and contains()
to manage CSS classes.
Example:
<div id="myDiv" class="container">Content</div>
const element = document.getElementById('myDiv');
// Adding a class
element.classList.add('highlight');
// Removing a class
element.classList.remove('container');
// Checking if a class exists
if (element.classList.contains('highlight')) {
// Do something
}
// Toggling a class
element.classList.toggle('active');
27.Use requestAnimationFrame()
for Smooth Animations: When creating animations, use requestAnimationFrame()
to ensure smooth and efficient animations that run at the optimal frame rate.
Example:
function animate() {
// Code to update the animation
requestAnimationFrame(animate);
}
// Start the animation
animate();
28.Avoid Synchronous AJAX Requests: Avoid using synchronous XMLHttpRequest (XHR) as it can block the main thread, causing a poor user experience. Instead, use asynchronous requests with Promises, async/await
, or callbacks.
Example using Promises:
function fetchData() {
return fetch('https://api.example.com/data')
.then(response => response.json());
}
fetchData()
.then(data => console.log(data))
.catch(error => console.error('Error fetching data:', error));
Example using async/await
:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
throw new Error('Error fetching data:', error);
}
}
(async () => {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error.message);
}
})();
Thanks for reading :)