ES6 (also called ES2015) brought major improvements to JavaScript. If you're still writing ES5 code, you're missing out on features that make your code cleaner, shorter, and easier to understand. Let me show you the essential ES6 features every developer should know.
Arrow Functions
Arrow functions give you a shorter syntax for writing functions. But they're not just syntactic sugar - they also handle this differently.
// Old way
function add(a, b) {
return a + b
}
// ES6 way
const add = (a, b) => a + b
// With one parameter, you can skip parentheses
const double = n => n * 2
// With multiple lines, use curly braces
const greet = name => {
const message = `Hello, ${name}!`
return message
}The big difference: arrow functions don't have their own this. They inherit it from the parent scope. This solves a lot of common JavaScript headaches:
// Old way - needed bind or self = this
function Timer() {
this.seconds = 0
setInterval(function() {
this.seconds++ // Doesn't work - wrong 'this'
}.bind(this), 1000)
}
// ES6 way - just works
function Timer() {
this.seconds = 0
setInterval(() => {
this.seconds++ // Works perfectly!
}, 1000)
}Template Literals
Forget concatenating strings with +. Template literals make it easy to embed variables and expressions:
const name = 'John'
const age = 30
// Old way
const message = 'Hello, my name is ' + name + ' and I am ' + age + ' years old.'
// ES6 way
const message = `Hello, my name is ${name} and I am ${age} years old.`
// You can even do math
const total = `Total: $${price * quantity}`
// Multi-line strings are easy
const html = `
<div>
<h1>${title}</h1>
<p>${content}</p>
</div>
`Destructuring
Destructuring lets you extract values from arrays or properties from objects in one line. It's incredibly useful:
// Object destructuring
const user = {
name: 'John',
age: 30,
email: 'john@example.com'
}
// Old way
const name = user.name
const age = user.age
// ES6 way
const { name, age } = user
// With different variable names
const { name: userName, age: userAge } = user
// With default values
const { name, country = 'USA' } = user
// Array destructuring
const colors = ['red', 'green', 'blue']
const [first, second] = colors // first = 'red', second = 'green'
// Skip elements
const [, , third] = colors // third = 'blue'
// Swap variables
let a = 1, b = 2;
[a, b] = [b, a] // Now a = 2, b = 1Spread Operator
The spread operator (...) expands arrays or objects. It's perfect for copying or combining:
// Copy arrays
const original = [1, 2, 3]
const copy = [...original]
// Combine arrays
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const combined = [...arr1, ...arr2] // [1, 2, 3, 4, 5, 6]
// Copy objects
const user = { name: 'John', age: 30 }
const userCopy = { ...user }
// Merge objects
const defaults = { theme: 'light', lang: 'en' }
const settings = { lang: 'es', notifications: true }
const config = { ...defaults, ...settings }
// Result: { theme: 'light', lang: 'es', notifications: true }
// Function arguments
const numbers = [1, 5, 3, 9, 2]
Math.max(...numbers) // 9Rest Parameters
Rest parameters collect multiple arguments into an array. Similar syntax to spread, different use:
// Collect all arguments
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0)
}
sum(1, 2, 3, 4) // 10
// With other parameters
function greet(greeting, ...names) {
return `${greeting}, ${names.join(' and ')}!`
}
greet('Hello', 'John', 'Jane', 'Bob')
// "Hello, John and Jane and Bob!"Default Parameters
Set default values for function parameters easily:
// Old way
function greet(name) {
name = name || 'Guest'
return 'Hello ' + name
}
// ES6 way
function greet(name = 'Guest') {
return `Hello ${name}`
}
// With expressions
function createUser(name, role = 'user', id = Date.now()) {
return { name, role, id }
}Promises
Promises make asynchronous code much cleaner than callbacks:
// Creating a promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { user: 'John' }
resolve(data) // or reject(new Error('Failed'))
}, 1000)
})
}
// Using promises
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error))
// Chaining
fetchData()
.then(data => {
console.log(data)
return processData(data)
})
.then(processed => {
console.log(processed)
})
.catch(error => console.error(error))Async/Await
Async/await makes asynchronous code look synchronous. It's built on promises but much cleaner:
// Using async/await
async function loadUser() {
try {
const response = await fetch('/api/user')
const data = await response.json()
console.log(data)
return data
} catch (error) {
console.error('Failed to load user:', error)
}
}
// Multiple awaits
async function loadDashboard() {
const user = await fetchUser()
const posts = await fetchPosts(user.id)
const comments = await fetchComments(user.id)
return { user, posts, comments }
}
// Parallel requests
async function loadAll() {
const [user, posts, settings] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchSettings()
])
return { user, posts, settings }
}Classes
ES6 classes provide cleaner syntax for object-oriented programming:
class User {
constructor(name, email) {
this.name = name
this.email = email
}
greet() {
return `Hello, I'm ${this.name}`
}
get domain() {
return this.email.split('@')[1]
}
}
// Inheritance
class Admin extends User {
constructor(name, email, role) {
super(name, email)
this.role = role
}
deleteUser(userId) {
console.log(`Admin ${this.name} deleted user ${userId}`)
}
}
const admin = new Admin('John', 'john@example.com', 'superadmin')
admin.greet() // "Hello, I'm John"Modules (Import/Export)
ES6 modules let you organize code into separate files:
// utils.js
export const PI = 3.14159
export function square(x) {
return x * x
}
export default function cube(x) {
return x * x * x
}
// app.js
import cube, { PI, square } from './utils.js'
console.log(PI) // 3.14159
console.log(square(5)) // 25
console.log(cube(3)) // 27
// Import everything
import * as utils from './utils.js'
console.log(utils.PI)
console.log(utils.square(5))Enhanced Object Literals
Shorthand for object properties and methods:
const name = 'John'
const age = 30
// Old way
const user = {
name: name,
age: age,
greet: function() {
return 'Hello'
}
}
// ES6 way
const user = {
name, // same as name: name
age, // same as age: age
greet() { // shorthand method
return 'Hello'
},
// Computed property names
[`user_${Date.now()}`]: true
}Let and Const
Stop using var. Use const for variables that won't be reassigned, let for everything else:
// const - can't be reassigned
const API_URL = 'https://api.example.com'
const user = { name: 'John' }
user.name = 'Jane' // OK - object properties can change
// user = {} // ERROR - can't reassign
// let - can be reassigned
let count = 0
count = 1 // OK
// Block scope (var doesn't have this)
if (true) {
let x = 10
const y = 20
// x and y only exist in this block
}
// console.log(x) // ERRORArray Methods
ES6 added powerful array methods. Here are the most useful:
const numbers = [1, 2, 3, 4, 5]
// map - transform each element
const doubled = numbers.map(n => n * 2) // [2, 4, 6, 8, 10]
// filter - keep elements that pass test
const evens = numbers.filter(n => n % 2 === 0) // [2, 4]
// find - get first match
const firstEven = numbers.find(n => n % 2 === 0) // 2
// reduce - combine elements
const sum = numbers.reduce((total, n) => total + n, 0) // 15
// some - check if any match
const hasEven = numbers.some(n => n % 2 === 0) // true
// every - check if all match
const allPositive = numbers.every(n => n > 0) // true
// includes - check if value exists
numbers.includes(3) // trueFinal Thoughts
These ES6 features aren't just nice to have - they're essential for modern JavaScript development. Start using them today! Your code will be cleaner, shorter, and easier to maintain. The syntax might feel weird at first, but give it a week and you'll never want to go back to ES5.