Skip to content

ES6+ JavaScript Features: Complete Guide with Examples

Comprehensive guide to ES6+ JavaScript features including arrow functions, destructuring, promises, async/await, modules, and more with practical examples.

By 12 min read
  • JavaScript
  • ES6
  • ES2015
  • Modern JavaScript
  • Frontend
  • Web Development
ES6+ JavaScript Features: Complete Guide with Examples

ES6 (ECMAScript 2015) revolutionized JavaScript, introducing features that are now fundamental to modern web development. Whether you're working with React, Angular, Vue, or vanilla JavaScript, understanding these features is essential. Here's a comprehensive guide with practical examples.

1. Arrow Functions

Arrow functions provide concise syntax and lexical this binding:

JAVASCRIPT
// Traditional function
function add(a, b) {
  return a + b;
}

// Arrow function
const add = (a, b) => a + b;

// Single parameter (no parentheses needed)
const square = x => x * x;

// No parameters
const greet = () => 'Hello';

// Multiple statements
const process = (data) => {
  const filtered = data.filter(item => item.active);
  return filtered.map(item => item.name);
};

// Lexical this binding
class Counter {
  constructor() {
    this.count = 0;
  }

  // Traditional function - this is bound to caller
  incrementOld() {
    setInterval(function() {
      this.count++; // Error: this is undefined
    }, 1000);
  }

  // Arrow function - this is bound to Counter instance
  increment() {
    setInterval(() => {
      this.count++; // Works correctly
    }, 1000);
  }
}

2. Destructuring

Extract values from arrays and objects:

JAVASCRIPT
// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
// first = 1, second = 2, rest = [3, 4, 5]

// Object destructuring
const user = {
  id: 1,
  name: 'John',
  email: 'john@example.com',
  role: 'admin'
};

const { name, email, role } = user;
// name = 'John', email = 'john@example.com', role = 'admin'

// Renaming variables
const { name: userName, email: userEmail } = user;

// Default values
const { name, email, phone = 'N/A' } = user;

// Nested destructuring
const config = {
  api: {
    baseUrl: 'https://api.example.com',
    timeout: 5000
  }
};

const { api: { baseUrl, timeout } } = config;

// Function parameters
function greet({ name, age = 18 }) {
  return `Hello, ${name}! You are ${age} years old.`;
}

greet({ name: 'John' }); // "Hello, John! You are 18 years old."

3. Template Literals

Create strings with embedded expressions:

JAVASCRIPT
// Basic template literal
const name = 'John';
const greeting = `Hello, ${name}!`;

// Multi-line strings
const message = `
  This is a
  multi-line
  string
`;

// Expressions
const a = 10;
const b = 20;
const sum = `The sum of ${a} and ${b} is ${a + b}`;

// Tagged templates
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => {
    return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
  }, '');
}

const name = 'John';
const age = 30;
highlight`Hello, ${name}! You are ${age} years old.`;
// "Hello, <mark>John</mark>! You are <mark>30</mark> years old."

4. Spread and Rest Operators

Spread arrays/objects and collect remaining items:

JAVASCRIPT
// Spread operator - arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// Copy array
const original = [1, 2, 3];
const copy = [...original];

// Spread operator - objects
const user = { name: 'John', age: 30 };
const updated = { ...user, age: 31 }; // { name: 'John', age: 31 }

// Merge objects
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
const config = { ...defaults, ...userPrefs }; // { theme: 'dark', lang: 'en' }

// Rest operator - function parameters
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

sum(1, 2, 3, 4, 5); // 15

// Rest in destructuring
const [first, ...rest] = [1, 2, 3, 4, 5];
// first = 1, rest = [2, 3, 4, 5]

const { name, ...otherProps } = user;
// name = 'John', otherProps = { age: 30, email: '...' }

5. Default Parameters

Set default values for function parameters:

JAVASCRIPT
// Default parameters
function greet(name = 'Guest', greeting = 'Hello') {
  return `${greeting}, ${name}!`;
}

greet(); // "Hello, Guest!"
greet('John'); // "Hello, John!"
greet('John', 'Hi'); // "Hi, John!"

// Default with destructuring
function createUser({
  name = 'Anonymous',
  email = '',
  role = 'user',
  active = true
} = {}) {
  return { name, email, role, active };
}

createUser(); // { name: 'Anonymous', email: '', role: 'user', active: true }
createUser({ name: 'John' }); // { name: 'John', email: '', role: 'user', active: true }

6. Promises

Handle asynchronous operations:

JAVASCRIPT
// Creating a promise
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { id: 1, name: 'John' };
      resolve(data);
      // or reject(new Error('Failed to fetch'));
    }, 1000);
  });
};

// Using promises
fetchData()
  .then(data => {
    console.log('Data:', data);
    return processData(data);
  })
  .then(processed => {
    console.log('Processed:', processed);
  })
  .catch(error => {
    console.error('Error:', error);
  })
  .finally(() => {
    console.log('Cleanup');
  });

// Promise.all - wait for all
Promise.all([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3)
])
  .then(users => {
    console.log('All users:', users);
  })
  .catch(error => {
    console.error('One request failed:', error);
  });

// Promise.allSettled - wait for all (success or failure)
Promise.allSettled([
  fetchUser(1),
  fetchUser(2),
  fetchUser(999) // might fail
])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('Success:', result.value);
      } else {
        console.error('Failed:', result.reason);
      }
    });
  });

// Promise.race - first to resolve/reject
Promise.race([
  fetchFromAPI(),
  timeout(5000)
])
  .then(result => {
    console.log('First result:', result);
  });

7. Async/Await

Syntactic sugar for promises:

JAVASCRIPT
// Async function
async function fetchUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchUserPosts(userId);
    const comments = await fetchUserComments(userId);
    
    return {
      user,
      posts,
      comments
    };
  } catch (error) {
    console.error('Error fetching user data:', error);
    throw error;
  }
}

// Using async/await
async function displayUser(userId) {
  try {
    const data = await fetchUserData(userId);
    console.log('User:', data.user);
    console.log('Posts:', data.posts);
  } catch (error) {
    console.error('Failed to load user:', error);
  }
}

// Parallel async operations
async function loadUserData(userId) {
  const [user, posts, comments] = await Promise.all([
    fetchUser(userId),
    fetchUserPosts(userId),
    fetchUserComments(userId)
  ]);
  
  return { user, posts, comments };
}

8. Modules (import/export)

Organize code into reusable modules:

JAVASCRIPT
// math.js - Named exports
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;

// Or export at the end
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
export { add, subtract };

// Default export
export default function calculate(operation, a, b) {
  // Implementation
}

// user.js - Mixed exports
export const DEFAULT_ROLE = 'user';

export function createUser(name, email) {
  return { name, email, role: DEFAULT_ROLE };
}

export default class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

// Importing
import { add, subtract } from './math.js';
import calculate from './math.js'; // default import
import * as math from './math.js'; // namespace import
import { add as sum } from './math.js'; // renamed import

// Dynamic import
const module = await import('./math.js');
const result = module.add(1, 2);

9. Classes

Object-oriented programming in JavaScript:

JAVASCRIPT
// Class declaration
class User {
  // Private fields (ES2022)
  #password;
  
  // Static property
  static count = 0;
  
  // Constructor
  constructor(name, email, password) {
    this.name = name;
    this.email = email;
    this.#password = password;
    User.count++;
  }
  
  // Getter
  get displayName() {
    return `${this.name} (${this.email})`;
  }
  
  // Setter
  set email(newEmail) {
    if (this.isValidEmail(newEmail)) {
      this.email = newEmail;
    }
  }
  
  // Method
  greet() {
    return `Hello, ${this.name}!`;
  }
  
  // Private method
  #isValidEmail(email) {
    return email.includes('@');
  }
  
  // Static method
  static getCount() {
    return User.count;
  }
}

// Inheritance
class Admin extends User {
  constructor(name, email, password, permissions) {
    super(name, email, password);
    this.permissions = permissions;
  }
  
  // Override method
  greet() {
    return `Hello, Admin ${this.name}!`;
  }
  
  // New method
  deleteUser(userId) {
    // Implementation
  }
}

// Usage
const user = new User('John', 'john@example.com', 'secret');
const admin = new Admin('Jane', 'jane@example.com', 'secret', ['delete', 'edit']);

10. Map and Set

Better alternatives to objects and arrays for specific use cases:

JAVASCRIPT
// Map - key-value pairs with any key type
const userMap = new Map();

userMap.set('user1', { name: 'John', age: 30 });
userMap.set(1, { name: 'Jane', age: 25 });
userMap.set({ id: 1 }, { name: 'Bob', age: 35 });

userMap.get('user1'); // { name: 'John', age: 30 }
userMap.has('user1'); // true
userMap.delete('user1');
userMap.size; // 2

// Iterate Map
for (const [key, value] of userMap) {
  console.log(key, value);
}

userMap.forEach((value, key) => {
  console.log(key, value);
});

// Set - unique values
const uniqueNumbers = new Set([1, 2, 3, 3, 4, 4, 5]);
// uniqueNumbers = Set { 1, 2, 3, 4, 5 }

uniqueNumbers.add(6);
uniqueNumbers.has(3); // true
uniqueNumbers.delete(3);
uniqueNumbers.size; // 4

// Convert array to Set and back
const array = [1, 2, 2, 3, 3, 4];
const unique = [...new Set(array)]; // [1, 2, 3, 4]

11. Array Methods

Powerful array manipulation methods:

JAVASCRIPT
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 array
const names = users.map(user => user.name);
// ['John', 'Jane', 'Bob']

// filter - select items
const activeUsers = users.filter(user => user.active);
// [{ id: 1, ... }, { id: 3, ... }]

// find - find first match
const user = users.find(user => user.id === 2);
// { id: 2, name: 'Jane', ... }

// findIndex - find index
const index = users.findIndex(user => user.id === 2);
// 1

// some - check if any match
const hasActiveUsers = users.some(user => user.active);
// true

// every - check if all match
const allActive = users.every(user => user.active);
// false

// reduce - accumulate values
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
// 90

// flat - flatten nested arrays
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.flat(); // [1, 2, 3, 4, 5, 6]

// flatMap - map then flatten
const words = ['hello world', 'goodbye'];
const letters = words.flatMap(word => word.split(' '));
// ['hello', 'world', 'goodbye']

12. Optional Chaining and Nullish Coalescing

Safe property access and default values:

JAVASCRIPT
// Optional chaining (?.)
const user = {
  profile: {
    address: {
      city: 'Pune'
    }
  }
};

// Safe access
const city = user?.profile?.address?.city; // 'Pune'
const zip = user?.profile?.address?.zip; // undefined (no error)

// With function calls
const result = api?.getData?.(); // Safe function call

// Nullish coalescing (??)
const name = user.name ?? 'Anonymous';
const count = items?.length ?? 0;
const theme = settings?.theme ?? 'light';

// Difference from ||
const value = 0;
const result1 = value || 10; // 10 (0 is falsy)
const result2 = value ?? 10; // 0 (only null/undefined use default)

// Combined
const city = user?.profile?.address?.city ?? 'Unknown';

13. Object Methods

Modern object manipulation:

JAVASCRIPT
const user = { name: 'John', age: 30, email: 'john@example.com' };

// Object.keys
Object.keys(user); // ['name', 'age', 'email']

// Object.values
Object.values(user); // ['John', 30, 'john@example.com']

// Object.entries
Object.entries(user); // [['name', 'John'], ['age', 30], ['email', 'john@example.com']]

// Object.assign - merge objects
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
const config = Object.assign({}, defaults, userPrefs);
// { theme: 'dark', lang: 'en' }

// Object.freeze - prevent modifications
const frozen = Object.freeze(user);
frozen.name = 'Jane'; // Ignored in strict mode

// Object.seal - prevent adding/deleting properties
const sealed = Object.seal(user);
sealed.phone = '123'; // Ignored
sealed.name = 'Jane'; // Allowed

14. Symbols

Create unique identifiers:

JAVASCRIPT
// Create symbol
const id = Symbol('id');
const id2 = Symbol('id');

id === id2; // false (always unique)

// Use as object key
const user = {
  [id]: 123,
  name: 'John'
};

// Symbol.for - global symbol registry
const globalId = Symbol.for('id');
const sameId = Symbol.for('id');
globalId === sameId; // true

// Well-known symbols
const iterable = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next() {
        count++;
        return count <= 3
          ? { value: count, done: false }
          : { done: true };
      }
    };
  }
};

for (const value of iterable) {
  console.log(value); // 1, 2, 3
}

15. Generators

Functions that can be paused and resumed:

JAVASCRIPT
// Generator function
function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numberGenerator();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: undefined, done: true }

// Infinite generator
function* infiniteNumbers() {
  let num = 0;
  while (true) {
    yield num++;
  }
}

const numbers = infiniteNumbers();
numbers.next().value; // 0
numbers.next().value; // 1
numbers.next().value; // 2

// Generator with parameters
function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacci();
fib.next().value; // 1
fib.next().value; // 1
fib.next().value; // 2
fib.next().value; // 3

Best Practices

  1. Use const by default: Only use let when reassignment is needed
  2. Prefer arrow functions: For callbacks and when this binding matters
  3. Use template literals: Instead of string concatenation
  4. Destructure objects: Extract only what you need
  5. Use async/await: Instead of promise chains when possible
  6. Leverage array methods: map, filter, reduce for data transformation
  7. Use optional chaining: For safe property access
  8. Prefer Map/Set: When order or uniqueness matters

Real-World Impact

ES6+ features have:

  • Made JavaScript more expressive and readable
  • Reduced boilerplate code significantly
  • Improved async code handling
  • Enhanced code organization with modules
  • Made JavaScript more suitable for large applications

Mastering these ES6+ features is essential for modern JavaScript development. They're not just syntax sugar—they fundamentally change how you write and think about JavaScript code.

Keep reading