09/02/2026⏱️ 14 min read
Modern JavaScript: ES6+ Features Every Developer Should Know
JavaScriptES6Modern JavaScriptWeb DevelopmentFrontendProgramming

Modern JavaScript: ES6+ Features Every Developer Should Know

Introduction

JavaScript has evolved significantly since ES6 (ECMAScript 2015), introducing powerful features that have transformed how we write modern web applications. These features not only make code more concise and readable but also provide better performance and developer experience.
This comprehensive guide covers the most important ES6+ features that every JavaScript developer should master. From arrow functions and destructuring to async/await and modules, you'll learn how to write more efficient, maintainable, and modern JavaScript code.

Arrow Functions

Arrow functions provide a more concise syntax for writing function expressions:
Basic Syntax:
javascript
// 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 World!'; // Multiple statements const processData = (data) => { const processed = data.map(item => item * 2); return processed.filter(item => item > 10); };
Key Differences:
  • Lexical this: Arrow functions don't have their own this binding
  • No arguments object: Use rest parameters instead
  • Cannot be used as constructors: No prototype property
  • Cannot be hoisted: Must be declared before use
Common Use Cases:
javascript
// Array methods const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); const evens = numbers.filter(n => n % 2 === 0); const sum = numbers.reduce((acc, n) => acc + n, 0); // Event handlers button.addEventListener('click', (event) => { console.log('Button clicked!'); }); // Promises fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error));

Destructuring Assignment

Destructuring allows you to extract values from arrays or objects into distinct variables:
Array Destructuring:
javascript
// Basic destructuring const colors = ['red', 'green', 'blue']; const [first, second, third] = colors; console.log(first); // 'red' console.log(second); // 'green' console.log(third); // 'blue' // Skip elements const [first, , third] = colors; console.log(first); // 'red' console.log(third); // 'blue' // Default values const [first, second, third, fourth = 'yellow'] = colors; console.log(fourth); // 'yellow' // Rest operator const [first, ...rest] = colors; console.log(first); // 'red' console.log(rest); // ['green', 'blue'] // Swap variables let a = 1, b = 2; [a, b] = [b, a]; console.log(a, b); // 2, 1
Object Destructuring:
javascript
// Basic destructuring const person = { name: 'John', age: 30, city: 'New York' }; const { name, age, city } = person; console.log(name); // 'John' console.log(age); // 30 console.log(city); // 'New York' // Rename variables const { name: fullName, age: years } = person; console.log(fullName); // 'John' console.log(years); // 30 // Default values const { name, age, country = 'USA' } = person; console.log(country); // 'USA' // Nested destructuring const user = { id: 1, profile: { name: 'John', email: 'john@example.com' }, settings: { theme: 'dark', notifications: true } }; const { id, profile: { name, email }, settings: { theme } } = user; console.log(id, name, email, theme); // Function parameters function greetUser({ name, age = 18 }) { console.log(`Hello ${name}, you are ${age} years old`); } greetUser({ name: 'John' }); // Hello John, you are 18 years old

Template Literals

Template literals provide an easy way to create strings with embedded expressions:
Basic Syntax:
javascript
// Traditional string concatenation const name = 'John'; const age = 30; const message = 'Hello, my name is ' + name + ' and I am ' + age + ' years old'; // Template literal const message = `Hello, my name is ${name} and I am ${age} years old`; // Multi-line strings const html = ` <div class="user-card"> <h2>${name}</h2> <p>Age: ${age}</p> </div> `; // Expressions in templates const price = 19.99; const tax = 0.08; const total = `Total: $${(price * (1 + tax)).toFixed(2)}`; console.log(total); // Total: $21.59 // Tagged templates function highlight(strings, ...values) { return strings.reduce((result, string, i) => { const value = values[i] ? `<mark>${values[i]}</mark>` : ''; return result + string + value; }, ''); } const name = 'John'; const age = 30; const highlighted = highlight`Hello ${name}, you are ${age} years old`; console.log(highlighted); // Hello <mark>John</mark>, you are <mark>30</mark> years old
Common Use Cases:
javascript
// SQL queries const userId = 123; const query = ` SELECT * FROM users WHERE id = ${userId} AND status = 'active' `; // API URLs const baseUrl = 'https://api.example.com'; const endpoint = 'users'; const url = `${baseUrl}/${endpoint}/${userId}`; // HTML templates function createUserCard(user) { return ` <div class="user-card" data-id="${user.id}"> <img src="${user.avatar}" alt="${user.name}"> <h3>${user.name}</h3> <p>${user.email}</p> <span class="status ${user.status}">${user.status}</span> </div> `; }

Spread and Rest Operators

The spread (...) and rest operators provide powerful ways to work with arrays and objects:
Spread Operator:
javascript
// Array spreading const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const combined = [...arr1, ...arr2]; console.log(combined); // [1, 2, 3, 4, 5, 6] // Copy arrays const original = [1, 2, 3]; const copy = [...original]; copy.push(4); console.log(original); // [1, 2, 3] console.log(copy); // [1, 2, 3, 4] // Function arguments const numbers = [1, 2, 3, 4, 5]; const max = Math.max(...numbers); console.log(max); // 5 // Object spreading const obj1 = { a: 1, b: 2 }; const obj2 = { c: 3, d: 4 }; const combined = { ...obj1, ...obj2 }; console.log(combined); // { a: 1, b: 2, c: 3, d: 4 } // Object copying and updating const user = { name: 'John', age: 30, city: 'NYC' }; const updatedUser = { ...user, age: 31, country: 'USA' }; console.log(updatedUser); // { name: 'John', age: 31, city: 'NYC', country: 'USA' } // Shallow copy const original = { a: 1, b: { c: 2 } }; const copy = { ...original }; copy.b.c = 3; console.log(original.b.c); // 3 (shallow copy)
Rest Operator:
javascript
// Function parameters function sum(...numbers) { return numbers.reduce((acc, num) => acc + num, 0); } console.log(sum(1, 2, 3, 4, 5)); // 15 // Array destructuring const [first, second, ...rest] = [1, 2, 3, 4, 5]; console.log(first); // 1 console.log(second); // 2 console.log(rest); // [3, 4, 5] // Object destructuring const { name, age, ...otherProps } = { name: 'John', age: 30, city: 'NYC', country: 'USA' }; console.log(name); // 'John' console.log(age); // 30 console.log(otherProps); // { city: 'NYC', country: 'USA' } // Collecting arguments function logMessage(level, ...messages) { console.log(`[${level}]`, ...messages); } logMessage('INFO', 'User logged in', 'Session started'); // [INFO] User logged in Session started

Classes and Inheritance

ES6 introduced class syntax for object-oriented programming:
Basic Class Syntax:
javascript
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { return `Hello, my name is ${this.name}`; } get info() { return `${this.name} is ${this.age} years old`; } set age(newAge) { if (newAge < 0) { throw new Error('Age cannot be negative'); } this._age = newAge; } get age() { return this._age; } } const person = new Person('John', 30); console.log(person.greet()); // Hello, my name is John console.log(person.info); // John is 30 years old
Inheritance:
javascript
class Student extends Person { constructor(name, age, studentId) { super(name, age); // Call parent constructor this.studentId = studentId; } greet() { return `${super.greet()} and I am a student`; } study() { return `${this.name} is studying`; } } const student = new Student('Alice', 20, 'S12345'); console.log(student.greet()); // Hello, my name is Alice and I am a student console.log(student.study()); // Alice is studying
Static Methods and Properties:
javascript
class MathUtils { static PI = 3.14159; static add(a, b) { return a + b; } static multiply(a, b) { return a * b; } static circleArea(radius) { return this.PI * radius * radius; } } console.log(MathUtils.add(5, 3)); // 8 console.log(MathUtils.multiply(4, 6)); // 24 console.log(MathUtils.circleArea(5)); // 78.53975
Private Fields (ES2022):
javascript
class BankAccount { #balance = 0; // Private field #accountNumber; // Private field constructor(accountNumber, initialBalance = 0) { this.#accountNumber = accountNumber; this.#balance = initialBalance; } deposit(amount) { if (amount > 0) { this.#balance += amount; } } withdraw(amount) { if (amount > 0 && amount <= this.#balance) { this.#balance -= amount; } } getBalance() { return this.#balance; } } const account = new BankAccount('12345', 1000); account.deposit(500); console.log(account.getBalance()); // 1500 // console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class

Modules (ES6 Modules)

ES6 modules provide a standardized way to organize and share code:
Exporting:
javascript
// math.js - Named exports export const PI = 3.14159; export function add(a, b) { return a + b; } export function multiply(a, b) { return a * b; } // Default export export default function subtract(a, b) { return a - b; } // Alternative export syntax const divide = (a, b) => a / b; export { divide }; // Export multiple items const operations = { add, subtract, multiply, divide }; export { operations };
Importing:
javascript
// Import named exports import { add, multiply, PI } from './math.js'; // Import with alias import { add as addition, multiply as multiplication } from './math.js'; // Import default export import subtract from './math.js'; // Import all named exports import * as math from './math.js'; console.log(math.add(5, 3)); // 8 // Import default and named exports import subtract, { add, multiply } from './math.js'; // Dynamic imports async function loadModule() { const { add, multiply } = await import('./math.js'); console.log(add(5, 3)); // 8 }
Module Examples:
javascript
// user.js export class User { constructor(name, email) { this.name = name; this.email = email; } greet() { return `Hello, I'm ${this.name}`; } } export function createUser(name, email) { return new User(name, email); } export const DEFAULT_USER = new User('Guest', 'guest@example.com'); // utils.js export const formatDate = (date) => { return date.toLocaleDateString(); }; export const formatCurrency = (amount) => { return `$${amount.toFixed(2)}`; }; // main.js import { User, createUser, DEFAULT_USER } from './user.js'; import { formatDate, formatCurrency } from './utils.js'; const user = createUser('John', 'john@example.com'); console.log(user.greet()); // Hello, I'm John const today = new Date(); console.log(formatDate(today)); // 12/25/2023 console.log(formatCurrency(19.99)); // $19.99

Promises and Async/Await

Promises and async/await provide elegant ways to handle asynchronous operations:
Promises:
javascript
// Creating a Promise function fetchData(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onload = () => { if (xhr.status === 200) { resolve(JSON.parse(xhr.responseText)); } else { reject(new Error(`HTTP ${xhr.status}`)); } }; xhr.onerror = () => reject(new Error('Network error')); xhr.send(); }); } // Using Promises fetchData('/api/users') .then(data => { console.log('Users:', data); return fetchData('/api/posts'); }) .then(posts => { console.log('Posts:', posts); }) .catch(error => { console.error('Error:', error.message); }); // Promise.all - Wait for all promises Promise.all([ fetchData('/api/users'), fetchData('/api/posts'), fetchData('/api/comments') ]) .then(([users, posts, comments]) => { console.log('All data loaded:', { users, posts, comments }); }) .catch(error => { console.error('One or more requests failed:', error); }); // Promise.race - First promise to resolve Promise.race([ fetchData('/api/fast-endpoint'), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000)) ]) .then(data => { console.log('Fast response:', data); }) .catch(error => { console.error('Request failed or timed out:', error); });
Async/Await:
javascript
// Async function async function loadUserData(userId) { try { const user = await fetchData(`/api/users/${userId}`); const posts = await fetchData(`/api/users/${userId}/posts`); const comments = await fetchData(`/api/users/${userId}/comments`); return { user, posts, comments }; } catch (error) { console.error('Failed to load user data:', error); throw error; } } // Using async/await loadUserData(123) .then(data => { console.log('User data loaded:', data); }) .catch(error => { console.error('Error loading user data:', error); }); // Parallel async operations async function loadAllData() { try { const [users, posts, comments] = await Promise.all([ fetchData('/api/users'), fetchData('/api/posts'), fetchData('/api/comments') ]); return { users, posts, comments }; } catch (error) { console.error('Failed to load data:', error); throw error; } } // Async iteration async function processUsers() { const users = await fetchData('/api/users'); for (const user of users) { try { const userDetails = await fetchData(`/api/users/${user.id}/details`); console.log('User details:', userDetails); } catch (error) { console.error(`Failed to load details for user ${user.id}:`, error); } } }

Map, Set, and WeakMap/WeakSet

ES6 introduced new data structures for better data management:
Map:
javascript
// Creating a Map const userMap = new Map(); // Adding key-value pairs userMap.set('user1', { name: 'John', age: 30 }); userMap.set('user2', { name: 'Alice', age: 25 }); // Getting values console.log(userMap.get('user1')); // { name: 'John', age: 30 } // Checking if key exists console.log(userMap.has('user1')); // true // Getting size console.log(userMap.size); // 2 // Deleting entries userMap.delete('user1'); console.log(userMap.size); // 1 // Iterating over Map for (const [key, value] of userMap) { console.log(`${key}: ${value.name}`); } // Converting to Array const entries = Array.from(userMap.entries()); const keys = Array.from(userMap.keys()); const values = Array.from(userMap.values()); // Map vs Object const obj = {}; const map = new Map(); // Keys can be any type in Map map.set(1, 'one'); map.set('1', 'string one'); map.set({}, 'object key'); console.log(map.get(1)); // 'one' console.log(map.get('1')); // 'string one' console.log(map.get({})); // undefined (different object)
Set:
javascript
// Creating a Set const numbers = new Set([1, 2, 3, 4, 5]); const uniqueNames = new Set(['John', 'Alice', 'John', 'Bob']); console.log(uniqueNames); // Set(3) { 'John', 'Alice', 'Bob' } // Adding values numbers.add(6); numbers.add(1); // Won't add duplicate // Checking if value exists console.log(numbers.has(3)); // true // Getting size console.log(numbers.size); // 6 // Deleting values numbers.delete(3); console.log(numbers.has(3)); // false // Iterating over Set for (const number of numbers) { console.log(number); } // Set operations const set1 = new Set([1, 2, 3]); const set2 = new Set([2, 3, 4]); // Union const union = new Set([...set1, ...set2]); console.log(union); // Set(4) { 1, 2, 3, 4 } // Intersection const intersection = new Set([...set1].filter(x => set2.has(x))); console.log(intersection); // Set(2) { 2, 3 } // Difference const difference = new Set([...set1].filter(x => !set2.has(x))); console.log(difference); // Set(1) { 1 }
WeakMap and WeakSet:
javascript
// WeakMap - keys must be objects const weakMap = new WeakMap(); const obj1 = {}; const obj2 = {}; weakMap.set(obj1, 'value1'); weakMap.set(obj2, 'value2'); console.log(weakMap.get(obj1)); // 'value1' // WeakSet - values must be objects const weakSet = new WeakSet(); const obj3 = {}; const obj4 = {}; weakSet.add(obj3); weakSet.add(obj4); console.log(weakSet.has(obj3)); // true // WeakMap/WeakSet are not enumerable // They don't have size property // They don't have clear() method // They allow garbage collection of keys/values

Symbols

Symbols are unique, immutable primitive values that can be used as object keys:
Creating Symbols:
javascript
// Basic symbol const sym1 = Symbol(); const sym2 = Symbol('description'); const sym3 = Symbol('description'); console.log(sym1); // Symbol() console.log(sym2); // Symbol(description) console.log(sym2 === sym3); // false (each symbol is unique) // Global symbol registry const globalSym1 = Symbol.for('key'); const globalSym2 = Symbol.for('key'); console.log(globalSym1 === globalSym2); // true // Getting key from global symbol console.log(Symbol.keyFor(globalSym1)); // 'key'
Using Symbols as Object Keys:
javascript
// Creating symbols for object properties const id = Symbol('id'); const name = Symbol('name'); const user = { [id]: 123, [name]: 'John', age: 30 }; console.log(user[id]); // 123 console.log(user[name]); // 'John' console.log(user.age); // 30 // Symbols are not enumerable by default for (const key in user) { console.log(key); // Only 'age' } // Getting symbol properties const symbolKeys = Object.getOwnPropertySymbols(user); console.log(symbolKeys); // [Symbol(id), Symbol(name)] // All properties (including symbols) const allKeys = Reflect.ownKeys(user); console.log(allKeys); // ['age', Symbol(id), Symbol(name)]
Well-Known Symbols:
javascript
// Symbol.iterator - makes object iterable const iterable = { data: [1, 2, 3, 4, 5], [Symbol.iterator]() { let index = 0; const data = this.data; return { next() { if (index < data.length) { return { value: data[index++], done: false }; } else { return { done: true }; } } }; } }; // Now we can use for...of for (const value of iterable) { console.log(value); // 1, 2, 3, 4, 5 } // Symbol.toPrimitive - custom type conversion const customObject = { value: 42, [Symbol.toPrimitive](hint) { if (hint === 'number') { return this.value; } else if (hint === 'string') { return `Value: ${this.value}`; } else { return this.value; } } }; console.log(+customObject); // 42 (number) console.log(`${customObject}`); // 'Value: 42' (string) console.log(customObject + 1); // 43 (default)

Share this article

Comments