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.
- JavaScript
- ES6
- ES2015
- Modern JavaScript
- Frontend
- Web Development
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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
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:
// 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:
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:
// 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:
// 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
- Use const by default: Only use
letwhen reassignment is needed - Prefer arrow functions: For callbacks and when
thisbinding matters - Use template literals: Instead of string concatenation
- Destructure objects: Extract only what you need
- Use async/await: Instead of promise chains when possible
- Leverage array methods:
map,filter,reducefor data transformation - Use optional chaining: For safe property access
- 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
Your Frontend Shouldn't Trust a Single Click
Disabling the button is not enough. Why duplicate-submission protection has to exist on the client and the server, and what each layer is responsible for.
React 19: What's New and Why It Matters
Explore the groundbreaking features in React 19, including Actions, useOptimistic, and the new compiler that transforms how we build React applications.
Angular 18: Latest Features and What's New
Discover the latest features in Angular 18, including improved Signals, new control flow syntax, and enhanced developer experience.