A comprehensive guide to modern JavaScript best practices for cleaner, more maintainable, and performant code.
- Variable Declaration
- Classes and OOP
- Modern Function Syntax
- Null Handling
- Async Programming
- Object Operations
- Data Structures
- Internationalization
- Common Best Practices
- Testing
Use let
and const
instead of var
for block-scoping and predictable behavior.
// ❌ Avoid
var x = 1;
// ✅ Prefer
const PI = 3.14;
let count = 0;
// Block scoping example
for (let i = 0; i < 5; i++) {
console.log(i);
}
// console.log(i); // ReferenceError: i is not defined
// ❌ Avoid
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
// ✅ Prefer
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
// ❌ Avoid
class User {
constructor(name) {
this._name = name; // Convention only, not truly private
}
}
// ✅ Prefer
class User {
#name; // True private field
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
// ❌ Avoid
const numbers = [1, 2, 3];
numbers.map(function(num) {
return num * 2;
});
// ✅ Prefer
const numbers = [1, 2, 3];
numbers.map(num => num * 2);
// With this binding
class Calculator {
constructor() {
this.value = 0;
}
// Arrow function preserves 'this'
add = (x) => {
this.value += x;
}
}
// ❌ Avoid
const value = someValue || 'default';
// ✅ Prefer
const value = someValue ?? 'default';
// Example
const count = 0;
console.log(count || 10); // 10 (undesired)
console.log(count ?? 10); // 0 (correct)
// ❌ Avoid
const street = user && user.address && user.address.street;
// ✅ Prefer
const street = user?.address?.street;
// ❌ Avoid
function fetchData() {
return fetch('api/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => console.error(error));
}
// ✅ Prefer
async function fetchData() {
try {
const response = await fetch('api/data');
const data = await response.json();
return processData(data);
} catch (error) {
console.error(error);
}
}
const user = {
name: 'John',
age: 30,
city: 'New York'
};
// Object entries
Object.entries(user).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// Object values
const values = Object.values(user);
console.log(values); // ['John', 30, 'New York']
// Object keys
const keys = Object.keys(user);
console.log(keys); // ['name', 'age', 'city']
// ❌ Avoid
const userRoles = {};
const user = { id: 1 };
userRoles[user] = 'admin'; // user gets converted to string
// ✅ Prefer
const userRoles = new Map();
userRoles.set(user, 'admin');
console.log(userRoles.get(user)); // 'admin'
const PRIVATE_KEY = Symbol('privateKey');
class MyClass {
constructor() {
this[PRIVATE_KEY] = 'secret';
}
}
const instance = new MyClass();
console.log(Object.keys(instance)); // []
console.log(instance[PRIVATE_KEY]); // 'secret'
// Format currency
const amount = 123456.789;
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
console.log(formatter.format(amount)); // $123,456.79
// Format dates
const date = new Date();
const dateFormatter = new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
console.log(dateFormatter.format(date)); // 15 October 2024
// ❌ Avoid
console.log(0 == ''); // true (unexpected)
console.log([] == ![]); // true (very unexpected)
// ✅ Prefer
console.log(0 === ''); // false
console.log([] === ![]); // false
// ❌ Avoid
if (value) {
// ...
}
// ✅ Prefer
if (value !== null && value !== undefined) {
// ...
}
// ❌ Avoid direct JSON parse with large numbers
const data = JSON.parse('{"id": 9007199254740999}');
// ✅ Prefer custom parsing for large numbers
const safeData = JSON.parse('{"id": 9007199254740999}',
(key, value) => {
if (key === 'id') return BigInt(value);
return value;
}
);
import { test } from 'node:test';
import { equal } from 'node:assert';
const sum = (a, b) => a + b;
test('sum function', () => {
equal(sum(1, 1), 2);
equal(sum(-1, 1), 0);
equal(sum(0, 0), 0);
});
- Use modern syntax (let/const, classes, arrow functions)
- Leverage new JavaScript features for null handling
- Use async/await for asynchronous operations
- Prefer strict equality (===)
- Use built-in APIs before adding external dependencies
- Write tests using built-in test runners when possible
- Use proper documentation (JSDoc) for better code understanding
- Be cautious with floating-point calculations
- Use appropriate data structures (Map, Set, Symbol)
- Leverage the Intl API for formatting
Remember: Project-specific rules always take precedence over general best practices. Stay up-to-date with ECMAScript proposals and releases for the latest JavaScript features and conventions.