Practical guide to JavaScript best practices.

Practical guide to JavaScript best practices.

Introduction

Following JavaScript best practices is crucial for writing clean, efficient, and maintainable code. These practices enhance code readability, improve performance, and facilitate collaboration. This page outlines key best practices and provides code examples.


Avoid Globals (Use let and const)

  • Rationale: Global variables can lead to unintended side effects and complicate debugging.
  • Solution: Use let or const to declare variables within their respective scopes (e.g., function, block). const should be used for values that will not be reassigned.

Code Example:

// Bad: Global variable
var myGlobalVariable = 'some value';

// Good: Local variable
function processData() {
  let dataValue = 'local value'; // Can be reassigned
  const MAX_VALUE = 100; // Cannot be reassigned
  console.log(dataValue, MAX_VALUE);
}        

Avoid loose equality (double equals)

  • Rationale: The loose equality operator (==) performs type coercion, which can lead to unexpected and often confusing results.
  • Solution: Always use strict equality (===). The strict equality operator checks for both value and type equality without any type coercion.

Code Example:

// Loose Equality (Avoid)
console.log(1 == '1');     // true (string '1' is coerced to number 1)
console.log(0 == false);   // true (false is coerced to 0)
console.log(null == undefined); // true (a special case)
console.log([] == false) // true ([] is coerced to 0, false is coerced to 0)
console.log([] == ![]) // true ([] is coerced to 0, ![] is coerced to false which is 0)
console.log(null == 0) // false
console.log(undefined == 0) // false

// Strict Equality (Use This)
console.log(1 === '1');    // false (different types)
console.log(0 === false);  // false (different types)
console.log(null === undefined); // false
console.log([] === false) // false
console.log([] === ![]) // false
console.log(null === 0) // false
console.log(undefined === 0) // false

// Special cases to be aware of
console.log(NaN == NaN) // false
console.log(NaN === NaN) // false, the only way to check for NaN is Number.isNaN()
console.log(Number.isNaN(NaN)) // true        

Avoid JavaScript for Styling

  • Rationale: Separating styling from scripting improves maintainability and reduces complexity.
  • Solution: Utilize CSS for styling and leverage the ClassList API for dynamic style changes.

Code Example:

// Bad: Manipulating CSS directly
const element = document.getElementById('myElement');
element.style.color = 'red';

// Good: Using ClassList API
const element = document.getElementById('myElement');
element.classList.add('red'); 
        

Use Shorthands Carefully

  • Rationale: Shorthands can improve conciseness but should be used judiciously to maintain readability.
  • Solution: Use shorthands where appropriate but prioritize clarity.

Code Example:

let value = true;

// Longhand
if (value === true) {
  // ...
}

// Shorthand
if (value) {
  // ...
}

// Longhand (Function Declaration)
function addNumbers(a, b) {
  return a + b;
}

// Shorthand (Arrow Function)
const addNumbers = (a, b) => a + b;

//Shorthand (Ternary Operator)
const age = 20
const type = age >= 18 ? 'Adult' : 'Child'
        

Meaningful Names (Avoid Vague Terms)

  • Rationale: Descriptive names significantly improve code understanding. Avoid generic terms like "data," "util," or "service."
  • Solution: Use names that indicate the variable or function's purpose. Use camelCase for function names and PascalCase for classes.

// Bad:
let data = [1, 2, 3];
function process(d) { /* ... */ }

// Good:
let productQuantities = [1, 2, 3];
function calculateTotalQuantity(quantities) { /* ... */ }

class ProductManager{

}        

Initialize Variables

  • Rationale: Uninitialized variables can lead to unexpected behaviour and errors.
  • Solution: Always initialize variables with a default value.

Code Example:

// Bad:
let myVariable; 

// Good:
let myVariable = null; 
let myArray = [];        

Commenting and Documentation (Use JSDoc)

  • Rationale: Clear comments and documentation are essential for maintainability.
  • Solution: Use JSDoc-style comments for API documentation and tags.

Code Example:

/**
 * Calculates the area of a rectangle.
 * @param {number} width - The width of the rectangle.
 * @param {number} height - The height of the rectangle.
 * @returns {number} The area of the rectangle.
 * @throws {Error} If width or height is not a number.
 * @todo Implement input validation for negative values.
 */
function calculateRectangleArea(width, height) {
    if (typeof width !== 'number' || typeof height !== 'number') {
        throw new Error('Width and Height must be numbers.')
    }
  return width * height;
}        

Do Not Use the Object Constructor

  • Rationale: The new Object() constructor can lead to unexpected behaviour and is generally discouraged.
  • Solution: Use object literal syntax for creating objects.

Code Example:

// Bad:
const person = new Object();
person.name = 'John';

// Good:
const person = { name: 'John' };
        

Use for...of Instead of for Loops

  • Rationale: for...of loops are often more concise and readable, especially when iterating over arrays.
  • Solution: Use for...of loops whenever possible for better code clarity.

Code Example:

const numbers = [1, 2, 3, 4, 5];

// Bad:
for (let i = 0; i < numbers.length; i++) {
  console.log(numbers[i]);
}

// Good:
for (const number of numbers) {
  console.log(number);
}
        

Avoid Creating Irrelevant Classes (Single Responsibility Principle)

  • Rationale: Classes should have a single, well-defined purpose.
  • Solution: Adhere to the Single Responsibility Principle.

Code Example:

// Bad: A class that handles both user data and UI updates
// class UserInterfaceManager {
//   constructor(userData) { /* ... */ }
//   updateDisplay() { /* ... */ }
//   saveUserData() { /* ... */ }
// }

// Good: Separate classes for data handling and UI
class UserData {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

class UserDisplay {
    displayUser(userData) {
        console.log('User: ', userData.name)
    }
}
const user = new UserData("test", "[email protected]")
const display = new UserDisplay()
display.displayUser(user)
        

Class Declaration and Hoisting

  • Rationale: Classes are not hoisted.
  • Solution: Declare classes before using them.

Code Example:

// Bad:
// const myInstance = new MyClass(); // ReferenceError: Cannot access 'MyClass' before initialization

// class MyClass {
//   constructor() {}
// }

// Good:
class MyClass {
  constructor(name) {
      this.name = name
  }
  getName(){
      return this.name
  }
}

const myInstance = new MyClass("test");
console.log(myInstance.getName())
        

Helper Functions for Common Tasks

  • Rationale: Reusable helper functions improve code organization and reduce redundancy.
  • Solution: Create context-independent helper functions.

Code Example:

// Helper function to format dates
function formatDate(date) {
  return new Date(date).toLocaleDateString();
}

// Usage
const formattedDate = formatDate('2024-10-27');
console.log(formattedDate); // Output depends on locale
        

Asynchronous Code (Promises, async/await)

  • Rationale: Asynchronous operations improve performance by preventing blocking.
  • Solution: Use Promises and async/await for asynchronous code.

Code Example:

async function fetchData() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

fetchData();
        

Error Handling (try...catch)

  • Rationale: Proper error handling prevents application crashes.
  • Solution: Use try...catch blocks to handle potential errors.

Code Example:

function divide(a, b) {
  try {
    if (b === 0) {
      throw new Error('Division by zero!');
    }
    return a / b;
  } catch (error) {
    console.error('An error occurred:', error.message);
    return NaN; // Or handle the error in another way
  }
}

console.log(divide(10, 0)); // Output: An error occurred: Division by zero! NaN
        

Use the Appropriate Log Method

  • Rationale: Different log methods provide varying levels of information.
  • Solution: Use console.log(), console.warn(), console.error(), and console.debug() appropriately.

Code Example:

console.log('This is a regular log message.');
console.warn('This is a warning message.');
console.error('This is an error message.');
console.debug('This is a debug message.'); // Usually hidden unless debug mode is enabled
        

Use Safe and Reliable APIs

  • Rationale: Using modern, supported APIs ensures compatibility and security.
  • Solution: Avoid deprecated APIs.

Code Example:

// Bad: Using XMLHttpRequest (XHR)
// const xhr = new XMLHttpRequest(); // ...

// Good: Using fetch()
fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
        


Use Object.hasOwn() vs Object.prototype.hasOwnProperty()

  • Rationale: Object.hasOwn() is a safer and more concise method to check if an object has a property, avoiding issues when objects might have overridden prototypes.
  • Solution: Use Object.hasOwn() directly on the object instead of calling the method from the prototype.

Code Example:

if (Object.hasOwn(obj, 'prop')) {
    console.log('Property exists');
}        

Reference: MDN - Object.hasOwn()


Minimize Branching with Number()

  • Rationale: Converting values to numbers using Number() simplifies conditional checks and reduces if-else branching.
  • Solution: Use Number() to quickly convert values and handle different data types efficiently.

Code Example:

console.log(Number(undefined)); // NaN
console.log(Number(null));      // 0
console.log(Number(true));      // 1
console.log(Number(false));     // 0        

Reference: MDN - Number()


Use Array Methods Like every() and some()

  • Rationale: Array methods provide cleaner, more readable ways to validate and manipulate collections without complex loops.
  • Solution:

Code Example:

const numbers = [1, 2, 3, 4, 5];

console.log(numbers.some(num => num > 3)); // true
console.log(numbers.every(num => num > 0)); // true        

Reference: MDN - Array.every()


Document JavaScript Code Properly

  • Rationale: Well-documented code improves readability, maintainability, and collaboration within teams.
  • Solution: Use JSDoc for clear and standardized documentation.

JSDoc Example:

/**
 * Adds two numbers.
 * @param {number} a - The first number.
 * @param {number} b - The second number.
 * @returns {number} Sum of the two numbers.
 * @todo Handle non-numeric inputs.
 */
function add(a, b) {
    return a + b;
}
        

Reference: JSDoc


This is a more detailed and practical guide to JavaScript best practices. Whether you're a seasoned JavaScript developer or just starting your coding journey. Thanks!

Yours !!

Neeraj S

要查看或添加评论,请登录

Neeraj Swarnkar的更多文章

社区洞察

其他会员也浏览了