Essential ES6+ features every web developer should know: arrow functions, destructuring, template literals, async/await, and more.
ES6 (ECMAScript 2015) and later versions introduced powerful features that modernized JavaScript. Whether you're building Django templates with JavaScript or creating frontend apps, these features are essential to know.
Replace var with block-scoped variables:
// const - can't be reassigned (use by default)
const API_URL = 'https://api.example.com';
const user = { name: 'Alice' };
user.name = 'Bob'; // OK - mutating object is fine
// user = {}; // ERROR - can't reassign
// let - can be reassigned (use when value will change)
let count = 0;
count = 1; // OK
// Block scoping
if (true) {
let x = 10;
const y = 20;
}
// x and y don't exist here (unlike var)
// Traditional function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => a + b;
// With body
const processData = (data) => {
const filtered = data.filter(item => item.active);
return filtered.map(item => item.name);
};
// Great for callbacks
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
const name = 'Django';
const version = 5.1;
// Old way
const msg = 'Hello ' + name + ' v' + version;
// Template literal (use backticks)
const msg = `Hello ${name} v${version}`;
// Multi-line strings
const html = `
<div class="card">
<h2>${name}</h2>
<p>Version ${version}</p>
</div>
`;
// Object destructuring
const user = { name: 'Alice', age: 30, role: 'admin' };
const { name, age } = user;
console.log(name); // 'Alice'
// With renaming
const { name: userName, role: userRole } = user;
// With defaults
const { name, email = 'no email' } = user;
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first = 1, second = 2, rest = [3, 4, 5]
// In function parameters
function createUser({ name, email, role = 'user' }) {
console.log(`Creating ${name} with role ${role}`);
}
// Spread: expand arrays/objects
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const defaults = { theme: 'dark', lang: 'en' };
const settings = { ...defaults, lang: 'nl' };
// { theme: 'dark', lang: 'nl' }
// Rest: collect remaining arguments
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10
// Promise-based
fetch('/api/books/')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// Async/await (cleaner syntax)
async function getBooks() {
try {
const response = await fetch('/api/books/');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed:', error);
}
}
// Parallel requests
const [books, authors] = await Promise.all([
fetch('/api/books/').then(r => r.json()),
fetch('/api/authors/').then(r => r.json()),
]);
// utils.js - Named exports
export const formatPrice = (price) => `$${price.toFixed(2)}`;
export const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
// Default export
export default class ApiClient { ... }
// app.js - Import
import ApiClient from './utils.js';
import { formatPrice, capitalize } from './utils.js';
// Optional chaining (?.) - safely access nested properties
const city = user?.address?.city; // undefined if any part is null/undefined
const first = arr?.[0]; // Safe array access
const result = obj?.method?.(); // Safe method call
// Nullish coalescing (??) - default for null/undefined only
const name = user.name ?? 'Anonymous'; // Only if null/undefined
const count = data.count ?? 0; // '' and 0 are kept (unlike ||)
?? only falls back on null and undefined, while || falls back on any falsy value (including 0, '', false). Use ?? when zero or empty string are valid values.
const products = [
{ name: 'Widget', price: 25, inStock: true },
{ name: 'Gadget', price: 50, inStock: false },
{ name: 'Tool', price: 30, inStock: true },
];
// map - transform each element
const names = products.map(p => p.name);
// filter - keep matching elements
const available = products.filter(p => p.inStock);
// find - first match
const gadget = products.find(p => p.name === 'Gadget');
// some / every - boolean checks
products.some(p => p.price > 40); // true
products.every(p => p.inStock); // false
// reduce - accumulate
const total = products.reduce((sum, p) => sum + p.price, 0);
// includes
[1, 2, 3].includes(2); // true
Modern JavaScript features to adopt immediately:
const by default, let when reassignment is neededasync/await for readable asynchronous code