JavaScript Modern Patterns
March 1, 2025 32174b4 Edit this page
⚠️ Caution: Update Needed
261 day(s) old

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
  }
}

Resources