- JavaScript
- ES6
- Coding
10 ES6 Features Every JavaScript Developer Uses
The 10 ES2015 features used across modern JavaScript: let/const, arrow functions, destructuring, spread, modules, Promises, classes, Map and Set.
ES6, officially named ECMAScript 2015, is the edition of the JavaScript standard that introduced let and const, arrow functions, classes, modules, Promises and much more (ECMAScript 2015 specification). These ten features appear throughout modern JavaScript, from React components to Node.js servers. Each section below compares the older approach with the ES6 version and includes code you can run.
Key takeaways
- Use
constby default andletwhen a variable must be reassigned; both are block-scoped, unlikevar. - Arrow functions are shorter and take
thisfrom the surrounding scope. - Destructuring, template literals, default parameters and spread/rest syntax remove a lot of boilerplate.
- Modules, classes, Promises,
MapandSetgive JavaScript built-in structure for larger programs. - Object spread (
{ ...obj }) is not ES6: it arrived in ES2018, while ES6 added spread for arrays and function calls.
What is ES6?
ECMAScript is the standard that JavaScript implements. Its sixth edition was published in June 2015 and was the first major revision since ES5 in 2009. It changed how JavaScript is written day to day, and its features are the foundation of modern React, Vue and Node.js code.
Since 2015, the standard has been updated every year, with editions named by year (ES2016, ES2017 and so on). Here are the ES6 features you will use most.
1. Arrow functions =>
Arrow functions provide a concise syntax for writing function expressions. More importantly, they removed a long-standing headache with the this keyword.
The syntax cleanup
ES5:
var add = function(a, b) {
return a + b;
};
ES6 arrow function:
const add = (a, b) => a + b;
How do arrow functions handle this?
In a regular function, this depends on how the function is called. That often caused bugs in callbacks (like event listeners or timers), where this suddenly became the global object, or undefined in strict mode.
Arrow functions do not have their own this. They use the this of the scope they were defined in (lexical this).
class Timer {
constructor() {
this.seconds = 0;
// The arrow function uses the Timer instance as 'this'
const id = setInterval(() => {
this.seconds++;
console.log(this.seconds);
if (this.seconds === 3) clearInterval(id);
}, 1000);
}
}
new Timer(); // Logs 1, 2, 3
Because they have no this of their own and cannot be used with new, arrow functions are not a drop-in replacement for every function (MDN: arrow functions). For example, avoid them for object methods that need this.
2. Destructuring assignment
Destructuring lets you unpack values from arrays, or properties from objects, into separate variables. You see it constantly in React, for example const [count, setCount] = useState(0).
Object destructuring
const user = {
id: 42,
name: "Atish",
role: "Admin",
preferences: {
theme: "dark"
}
};
// Extract properties
const { name, role } = user;
// Extract nested properties
const { preferences: { theme } } = user;
// Set a default value for a missing property
const { status = "active" } = user;
console.log(name, role, theme, status); // Atish Admin dark active
Array destructuring
const coordinates = [10, 20, 30];
const [x, y] = coordinates;
console.log(x, y); // 10 20
3. Template literals
Template literals replace clumsy string concatenation with the + operator. They are wrapped in backticks and support embedded expressions and multi-line strings.
const name = "Atish";
const unreadCount = 3;
const isOnline = true;
// Before ES6
var oldGreeting = "Hello " + name + ", you have " + unreadCount + " messages.";
// ES6
const greeting = `Hello ${name}, you have ${unreadCount} messages.`;
// Any expression works inside ${}
const status = `User is ${isOnline ? "Online" : "Offline"}`;
// Multi-line strings without \n
const card = `
Name: ${name}
Status: ${isOnline ? "Online" : "Offline"}
`;
console.log(greeting);
console.log(status);
console.log(card);
4. Default parameters
Before ES6, you had to check for undefined inside the function body. Now you can define defaults directly in the function signature.
function createUser(name, role = "Guest", status = "Active") {
return { name, role, status };
}
console.log(createUser("Atish")); // { name: 'Atish', role: 'Guest', status: 'Active' }
console.log(createUser("Sam", "Admin")); // { name: 'Sam', role: 'Admin', status: 'Active' }
console.log(createUser("Kim", null)); // role stays null: defaults apply only to undefined
The { name, role, status } return value uses another ES6 feature, shorthand property names, which creates properties from variables with the same name.
5. Spread and rest syntax (...)
The three dots ... do two opposite jobs. Spread expands an array or other iterable into individual elements, and rest collects multiple values into an array.
Spread (expanding)
const numbers = [1, 2, 3];
// Copy and extend an array without mutating the original
const moreNumbers = [...numbers, 4, 5];
console.log(moreNumbers); // [ 1, 2, 3, 4, 5 ]
// Pass array items as separate arguments
console.log(Math.max(...numbers)); // 3
You will also see spread used on objects, which is common for immutable updates in React and Redux:
const oldObj = { a: 1, b: 2 };
const newObj = { ...oldObj, c: 3 };
console.log(newObj); // { a: 1, b: 2, c: 3 }
Object spread is not part of ES6, though. It was standardized later, in ECMAScript 2018, through the Rest/Spread Properties proposal (TC39 finished proposals).
Rest (collecting)
function sum(...args) {
return args.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
6. Modules (import / export)
Before ES6, JavaScript had no built-in module system, so developers relied on conventions such as CommonJS (require in Node.js) and AMD loaders like RequireJS. ES6 standardized modules.
math.js
export const add = (a, b) => a + b;
export const PI = 3.14159;
export default function calculator() {
console.log("Calculator loaded");
}
main.js
import calculator, { add, PI } from "./math.js";
calculator();
console.log(add(10, 5), PI); // 15 3.14159
Because import and export declarations are static, bundlers such as Vite and webpack can detect exports that are never used and remove them. This is called tree shaking. Note that modules need two separate files, so run this example in a project or in a page with <script type="module"> rather than a single-file sandbox.
7. Promises
ES6 standardized the Promise object for handling asynchronous operations, which helped replace deeply nested callbacks ("callback hell").
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data loaded");
}, 1000);
});
};
fetchData()
.then((data) => console.log(data))
.catch((error) => console.error(error));
(Note: ES2017 added async/await, which makes Promise-based code read like synchronous code. Promises are still the technology underneath. See mastering async/await in Node.js.)
8. Classes
JavaScript uses prototype-based inheritance, which can be confusing for developers coming from Java or C#. ES6 classes provide a cleaner syntax for creating objects and setting up inheritance.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks`);
}
}
const dog = new Dog("Rex");
dog.speak(); // Rex barks
Under the hood, classes still use prototypes, but the syntax is much easier to read and maintain.
9. Block-scoped variables: let and const
var is function-scoped, and its declarations are hoisted and initialized to undefined, so you can read a var before the line that declares it without getting an error. let and const are block-scoped: they exist only inside the nearest { }, and reading them before their declaration throws a ReferenceError.
The classic example is a loop with a delayed callback:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 0); // var: 3, var: 3, var: 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 0); // let: 0, let: 1, let: 2
}
With var, all three callbacks share one i. With let, each loop iteration gets its own j.
- Use
constby default. - Use
letonly if you need to reassign the value. - Avoid
varin new code.
10. Map and Set
ES6 added two dedicated collection types (MDN: Map):
Mapstores key-value pairs like an object, but keys can be any type (including objects), entries keep their insertion order, and the number of entries is available throughsize.Setstores unique values, so adding a value that is already there does nothing.
const visits = new Map();
const homePage = { path: "/" };
visits.set(homePage, 1); // An object as a key
visits.set("about", 5);
console.log(visits.get(homePage)); // 1
console.log(visits.size); // 2
const tags = new Set(["js", "css", "js"]);
tags.add("html");
console.log(tags.size); // 3
console.log([...tags]); // [ 'js', 'css', 'html' ]
// A common trick: remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3])];
console.log(unique); // [ 1, 2, 3 ]
Test it yourself
The best way to learn is to write code. Open the Node.js sandbox and paste in any example above (except modules, which need two files). Then try writing a class that uses arrow functions and destructuring.
New to the language? Start with JavaScript basics for beginners.
Frequently asked questions
What is the difference between ES6 and ECMAScript 2015?
There is no difference: they are two names for the same edition of the JavaScript standard. ES6 refers to the sixth edition of the ECMA-262 specification, and ECMAScript 2015 is its official name. Starting with that release, new editions have been published yearly and named by year, such as ES2016 and ES2017.
Is object spread part of ES6?
No. ES6 introduced spread syntax for arrays, function calls and other iterables, along with rest parameters. Spreading and collecting object properties with three dots came later, through the Rest/Spread Properties proposal finalized in ECMAScript 2018. Current browsers and Node.js support both, so the difference mostly matters when you target very old environments.
Do all browsers support ES6?
Current versions of Chrome, Firefox, Safari and Edge support the ES6 features in this article, and so do maintained Node.js releases. Internet Explorer never fully supported ES6, which is why older projects compiled their code to ES5 with tools such as Babel. Projects that target only modern browsers can usually skip that step.
Should I still use var in JavaScript?
In new code, use const by default and let when a value must be reassigned. Both are block-scoped and throw an error when used before their declaration, which catches mistakes that var silently allows. You will still see var in older code, so it is worth understanding how its function scope and hoisting work.
WebFiddle