ES6+ Features Guide

Master modern JavaScript with ES6 and beyond. Learn essential features that will make your code cleaner, more efficient, and easier to maintain.

What is ES6+?

ES6 (ECMAScript 2015) introduced significant improvements to JavaScript, and each year brings new features. These modern features help you write cleaner, more readable, and more efficient code.

This guide covers the most important and commonly used ES6+ features that every JavaScript developer should know.

Let & Const

ES6

Block-scoped variable declarations that replace var. Use const for values that won't be reassigned, and let for variables that will change.

Old Way (var)

var name = 'John';
var age = 30;
age = 31; // Can change

Modern Way

const name = 'John'; // Won't change
let age = 30;
age = 31; // Can change

When to Use:

  • Use const by default for all variables
  • Use let only when you need to reassign
  • Avoid var in modern JavaScript
  • Block scope prevents variable leaking
Supported in all modern browsers

Arrow Functions

ES6

A shorter syntax for writing functions with implicit returns and lexical this binding. Perfect for callbacks and functional programming.

Old Way

const add = function(a, b) {
  return a + b;
};

array.map(function(item) {
  return item * 2;
});

Arrow Functions

const add = (a, b) => a + b;


array.map(item => item * 2);
Advanced Examples:
// Single parameter (parentheses optional)
const double = x => x * 2;

// Multiple parameters
const sum = (a, b, c) => a + b + c;

// Multi-line body (requires return)
const processData = (data) => {
  const result = data.map(x => x * 2);
  return result.filter(x => x > 10);
};

// Returning object (wrap in parentheses)
const makePerson = (name, age) => ({ name, age });

When to Use:

  • Array methods (map, filter, reduce)
  • Callbacks and event handlers
  • Short, simple functions
  • When you need lexical this
Supported in all modern browsers

Template Literals

ES6

String interpolation and multi-line strings using backticks. Embed expressions directly in strings without concatenation.

Old Way

var name = 'John';
var age = 30;
var message = 'Hello, ' + name + 
  '! You are ' + age + ' years old.';

Template Literals

const name = 'John';
const age = 30;
const message = `Hello, ${name}!
You are ${age} years old.`;
Advanced Examples:
// Expression evaluation
const price = 19.99;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;

// Multi-line strings
const html = `
  <div class="card">
    <h2>${title}</h2>
    <p>${description}</p>
  </div>
`;

// Tagged templates
const highlight = (strings, ...values) => {
  return strings.reduce((result, str, i) => 
    `${result}${str}<mark>${values[i] || ''}</mark>`, '');
};
Supported in all modern browsers

Destructuring

ES6

Extract values from arrays or objects into distinct variables. Makes code more readable and eliminates repetitive property access.

Object Destructuring:
// Basic destructuring
const person = { name: 'John', age: 30, city: 'NYC' };
const { name, age, city } = person;

// With default values
const { name, country = 'USA' } = person;

// Renaming variables
const { name: fullName, age: years } = person;

// Nested destructuring
const user = { 
  id: 1, 
  profile: { name: 'John', email: 'john@example.com' }
};
const { profile: { name, email } } = user;
Array Destructuring:
// Basic destructuring
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;

// Skip elements
const [primary, , tertiary] = colors;

// Rest pattern
const [head, ...tail] = colors;

// Swapping variables
let a = 1, b = 2;
[a, b] = [b, a];
Supported in all modern browsers

Spread & Rest Operators

ES6

The spread operator (...) expands iterables, while the rest operator collects multiple elements. Same syntax, different contexts.

Spread Operator:
// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// Object spreading
const defaults = { theme: 'dark', fontSize: 14 };
const userPrefs = { fontSize: 16, language: 'en' };
const config = { ...defaults, ...userPrefs };
// { theme: 'dark', fontSize: 16, language: 'en' }

// Copying arrays/objects
const arrCopy = [...arr1];
const objCopy = { ...defaults };
Rest Parameters:
// Function rest parameters
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4); // 10

// Destructuring with rest
const [first, ...rest] = [1, 2, 3, 4];
// first = 1, rest = [2, 3, 4]

const { name, ...otherProps } = { name: 'John', age: 30, city: 'NYC' };
// name = 'John', otherProps = { age: 30, city: 'NYC' }
Supported in all modern browsers

Promises

ES6

Represent asynchronous operations, providing a cleaner alternative to callbacks. Promises have three states: pending, fulfilled, or rejected.

Creating & Using Promises:
// Creating a Promise
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      if (success) {
        resolve({ data: 'User data' });
      } else {
        reject('Failed to fetch');
      }
    }, 1000);
  });
};

// Using Promises
fetchData()
  .then(result => console.log(result.data))
  .catch(error => console.error(error))
  .finally(() => console.log('Done'));

// Promise chaining
fetchUser()
  .then(user => fetchPosts(user.id))
  .then(posts => displayPosts(posts))
  .catch(error => handleError(error));
Promise Methods:
// Promise.all - Wait for all
Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results));

// Promise.race - First to resolve
Promise.race([promise1, promise2])
  .then(result => console.log(result));

// Promise.allSettled - All results (ES2020)
Promise.allSettled([promise1, promise2])
  .then(results => console.log(results));
Supported in all modern browsers

Async/Await

ES2017

Syntactic sugar over Promises that makes asynchronous code look and behave like synchronous code. Much easier to read and debug.

Promises

function getUser() {
  return fetchUser()
    .then(user => fetchPosts(user.id))
    .then(posts => {
      return { user, posts };
    })
    .catch(error => {
      console.error(error);
    });
}

Async/Await

async function getUser() {
  try {
    const user = await fetchUser();
    const posts = await fetchPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error(error);
  }
}
Advanced Async/Await:
// Parallel execution
async function fetchAll() {
  const [users, posts, comments] = await Promise.all([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
  ]);
  return { users, posts, comments };
}

// Sequential execution
async function processItems(items) {
  for (const item of items) {
    await processItem(item);
  }
}

// Error handling
async function safeRequest() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Request failed:', error);
    return null;
  }
}
Supported in all modern browsers

Classes

ES6

Syntactic sugar over JavaScript's prototype-based inheritance. Provides a clearer and more familiar syntax for creating objects and handling inheritance.

Class Syntax:
// Basic class
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    return `Hello, I'm ${this.name}`;
  }
  
  // Getter
  get info() {
    return `${this.name} (${this.age})`;
  }
  
  // Setter
  set updateAge(newAge) {
    this.age = newAge;
  }
  
  // Static method
  static species() {
    return 'Homo sapiens';
  }
}

// Inheritance
class Employee extends Person {
  constructor(name, age, role) {
    super(name, age);
    this.role = role;
  }
  
  greet() {
    return `${super.greet()}, I'm a ${this.role}`;
  }
}

const dev = new Employee('John', 30, 'Developer');
console.log(dev.greet());
Supported in all modern browsers

Modules (Import/Export)

ES6

Split code into reusable modules with explicit imports and exports. Essential for organizing large applications and code reusability.

Exporting (utils.js):
// Named exports
export const PI = 3.14159;
export function add(a, b) {
  return a + b;
}
export class Calculator {
  // ...
}

// Export list
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };

// Default export
export default function subtract(a, b) {
  return a - b;
}
Importing (main.js):
// Named imports
import { PI, add, Calculator } from './utils.js';

// Import with alias
import { multiply as mult } from './utils.js';

// Import all
import * as utils from './utils.js';
console.log(utils.PI);

// Default import
import subtract from './utils.js';

// Mixed imports
import subtract, { add, multiply } from './utils.js';
Supported in all modern browsers (with type="module")

Best Practices & Tips

Start with Const

Always use const by default. Only use let when you know the variable will be reassigned. This makes your code more predictable and prevents accidental mutations.

Embrace Arrow Functions (But Know When Not To)

Arrow functions are great for most cases, but avoid them for object methods that need this binding, constructors, or when you need the arguments object.

Use Async/Await for Readability

Prefer async/await over raw Promises for better readability. It makes asynchronous code look synchronous and is easier to debug with standard try/catch blocks.

Destructure for Cleaner Code

Use destructuring to extract values from objects and arrays. It reduces code repetition and makes your intentions clearer to other developers.

Leverage Template Literals

Use template literals for any string that includes variables or spans multiple lines. They're more readable than concatenation and support embedded expressions.