JavaScript Modern Patterns
Modern JavaScript and ES6+ patterns and best practices
JavaScript Modern Patterns
Master modern JavaScript patterns using ES6+ features for cleaner, more maintainable code.
Destructuring
Object Destructuring
// Basic destructuring
const user = { name: 'John', age: 30, email: 'john@example.com' };
const { name, age } = user;
// Renaming variables
const { name: userName, age: userAge } = user;
// Default values
const { name, country = 'USA' } = user;
// Nested destructuring
const response = {
data: {
user: { id: 1, name: 'John' }
}
};
const { data: { user: { name } } } = response;
// Function parameters
function greet({ name, age }) {
console.log(`Hello ${name}, you are ${age} years old`);
}
greet(user);Array Destructuring
// Basic array destructuring
const colors = ['red', 'green', 'blue'];
const [primary, secondary] = colors;
// Skip elements
const [, , third] = colors;
// Rest operator
const [first, ...rest] = colors;
// Swapping variables
let a = 1, b = 2;
[a, b] = [b, a];
// Function returns
function getCoordinates() {
return [40.7128, -74.0060];
}
const [lat, lon] = getCoordinates();Arrow Functions
// Traditional function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => a + b;
// Single parameter (parentheses optional)
const square = x => x * x;
// No parameters
const greet = () => 'Hello!';
// Multiple lines
const processUser = user => {
const validated = validate(user);
const transformed = transform(validated);
return transformed;
};
// Returning object (wrap in parentheses)
const createUser = (name, age) => ({
name,
age,
createdAt: new Date()
});Arrow Functions and this
// Arrow functions don't bind their own 'this'
class Counter {
constructor() {
this.count = 0;
}
// Good: Arrow function preserves 'this'
increment = () => {
this.count++;
}
// Bad: Traditional function has different 'this'
incrementBad() {
setTimeout(function() {
this.count++; // 'this' is undefined!
}, 1000);
}
// Good: Arrow function in callback
incrementGood() {
setTimeout(() => {
this.count++; // 'this' refers to Counter instance
}, 1000);
}
}Async/Await
// Promise-based code
function fetchUser(id) {
return fetch(`/api/users/${id}`)
.then(response => response.json())
.then(data => data.user)
.catch(error => console.error(error));
}
// Async/await equivalent (cleaner!)
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return data.user;
} catch (error) {
console.error(error);
}
}
// Multiple parallel requests
async function fetchMultipleUsers(ids) {
try {
// Run in parallel
const promises = ids.map(id => fetch(`/api/users/${id}`));
const responses = await Promise.all(promises);
const users = await Promise.all(
responses.map(r => r.json())
);
return users;
} catch (error) {
console.error(error);
}
}
// Sequential vs Parallel
async function example() {
// Sequential (slower)
const user1 = await fetchUser(1);
const user2 = await fetchUser(2);
// Parallel (faster)
const [user1, user2] = await Promise.all([
fetchUser(1),
fetchUser(2)
]);
}Template Literals
// String interpolation
const name = 'John';
const age = 30;
const message = `Hello, ${name}! You are ${age} years old.`;
// Multi-line strings
const html = `
<div class="user">
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// Expressions
const price = 29.99;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
}, '');
}
const highlighted = highlight`Hello ${name}, you are ${age}!`;Spread and Rest Operators
Spread Operator
// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
// Array copying
const original = [1, 2, 3];
const copy = [...original];
// Object spreading
const user = { name: 'John', age: 30 };
const userWithEmail = { ...user, email: 'john@example.com' };
// Object merging (later properties override earlier)
const defaults = { theme: 'light', lang: 'en' };
const userSettings = { theme: 'dark' };
const settings = { ...defaults, ...userSettings };
// { theme: 'dark', lang: 'en' }
// Function arguments
const numbers = [1, 5, 3, 9, 2];
Math.max(...numbers); // 9
Rest Operator
// Function parameters
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4); // 10
// With other parameters
function greet(greeting, ...names) {
return `${greeting} ${names.join(', ')}!`;
}
greet('Hello', 'John', 'Jane', 'Bob');
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first: 1, second: 2, rest: [3, 4, 5]
// Object destructuring
const { name, age, ...otherInfo } = user;Optional Chaining
// Without optional chaining
const userName = user && user.profile && user.profile.name;
// With optional chaining
const userName = user?.profile?.name;
// With arrays
const firstPost = user?.posts?.[0];
// With functions
const result = obj.method?.();
// Combining with nullish coalescing
const displayName = user?.profile?.name ?? 'Anonymous';Nullish Coalescing
// || returns right side for ANY falsy value
const value1 = 0 || 'default'; // 'default'
const value2 = '' || 'default'; // 'default'
const value3 = false || 'default'; // 'default'
// ?? only returns right side for null/undefined
const value1 = 0 ?? 'default'; // 0
const value2 = '' ?? 'default'; // ''
const value3 = false ?? 'default'; // false
const value4 = null ?? 'default'; // 'default'
const value5 = undefined ?? 'default'; // 'default'
// Practical example
function getPort(config) {
return config?.port ?? 3000;
}Modules
Exporting
// Named exports
export const API_URL = 'https://api.example.com';
export function fetchUser(id) { /* ... */ }
export class User { /* ... */ }
// Export all at once
const API_URL = 'https://api.example.com';
function fetchUser(id) { /* ... */ }
class User { /* ... */ }
export { API_URL, fetchUser, User };
// Rename on export
export { fetchUser as getUser };
// Default export (one per file)
export default class User { /* ... */ }
// or
class User { /* ... */ }
export default User;Importing
// Named imports
import { API_URL, fetchUser } from './api.js';
// Rename on import
import { fetchUser as getUser } from './api.js';
// Import all
import * as api from './api.js';
api.fetchUser(1);
// Default import
import User from './User.js';
// Mix default and named
import User, { validateUser, UserRole } from './User.js';
// Dynamic imports
async function loadModule() {
const module = await import('./heavy-module.js');
module.doSomething();
}
// Conditional loading
if (condition) {
const { feature } = await import('./feature.js');
feature();
}Array Methods
const users = [
{ id: 1, name: 'John', age: 30, active: true },
{ id: 2, name: 'Jane', age: 25, active: false },
{ id: 3, name: 'Bob', age: 35, active: true }
];
// map - transform each element
const names = users.map(user => user.name);
// ['John', 'Jane', 'Bob']
// filter - keep elements that pass test
const activeUsers = users.filter(user => user.active);
// find - first element that passes test
const john = users.find(user => user.name === 'John');
// findIndex - index of first element that passes test
const johnIndex = users.findIndex(user => user.name === 'John');
// some - true if ANY element passes test
const hasActive = users.some(user => user.active);
// every - true if ALL elements pass test
const allActive = users.every(user => user.active);
// reduce - reduce to single value
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
// Sort (mutates original!)
const sorted = [...users].sort((a, b) => a.age - b.age);
// flatMap - map + flatten
const posts = [
{ id: 1, tags: ['js', 'web'] },
{ id: 2, tags: ['python', 'data'] }
];
const allTags = posts.flatMap(post => post.tags);
// ['js', 'web', 'python', 'data']
Object Shorthand
const name = 'John';
const age = 30;
// Property shorthand
const user = { name, age };
// Same as: { name: name, age: age }
// Method shorthand
const obj = {
// Old way
sayHello: function() {
return 'Hello';
},
// Shorthand
sayGoodbye() {
return 'Goodbye';
}
};
// Computed property names
const field = 'name';
const value = 'John';
const obj = {
[field]: value,
[`${field}Upper`]: value.toUpperCase()
};
// { name: 'John', nameUpper: 'JOHN' }
Classes
class User {
// Class fields
role = 'user';
#privateField = 'secret'; // Private field
constructor(name, age) {
this.name = name;
this.age = age;
}
// Method
greet() {
return `Hello, I'm ${this.name}`;
}
// Getter
get info() {
return `${this.name} (${this.age})`;
}
// Setter
set age(value) {
if (value < 0) throw new Error('Invalid age');
this._age = value;
}
// Static method
static create(name, age) {
return new User(name, age);
}
// Private method
#privateMethod() {
return this.#privateField;
}
}
// Inheritance
class Admin extends User {
constructor(name, age, permissions) {
super(name, age);
this.permissions = permissions;
this.role = 'admin';
}
// Override method
greet() {
return `${super.greet()}. I'm an admin!`;
}
}Promises
// Creating a promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation successful');
} else {
reject(new Error('Operation failed'));
}
}, 1000);
});
// Using promises
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Cleanup'));
// Promise.all - wait for all
Promise.all([promise1, promise2, promise3])
.then(results => {
// All succeeded
})
.catch(error => {
// At least one failed
});
// Promise.race - first to complete
Promise.race([promise1, promise2])
.then(result => {
// First one to complete
});
// Promise.allSettled - wait for all (regardless of success/failure)
Promise.allSettled([promise1, promise2])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log(result.value);
} else {
console.error(result.reason);
}
});
});Error Handling
// Try-catch
try {
const data = JSON.parse(invalidJson);
} catch (error) {
console.error('Parse error:', error.message);
} finally {
console.log('Cleanup');
}
// Custom errors
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email is required', 'email');
}
}
// Async error handling
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch failed:', error);
throw error; // Re-throw if needed
}
}