Key Takeaways
- ES6 means ECMAScript 2015, not every feature in modern JavaScript. Its features still form the backbone of everyday JavaScript.
- Start with let and const, template literals, arrow functions, destructuring, and parameter handling; then learn modules, classes, promises, and iteration.
- You can run these examples directly in a supported Node.js release without Babel.
- Choose Babel based on your target environments. Syntax transformation, runtime polyfills, and bundling solve different problems.
JavaScript ES6 is the common name for ECMAScript 2015, the sixth edition of the language standard. It's still worth learning because features such as block-scoped variables, arrow functions, modules, and promises remain foundational to modern JavaScript.
ECMAScript defines the language standard; JavaScript is the commonly used name for implementations of that standard. As a beginner, learn these features as ordinary JavaScript—not as a separate language you must finish before learning newer syntax.
What's New in ES6—and What Came Later?
The ES2015 specification introduced the features taught below. Later editions extended the language, so calling all modern JavaScript “ES6” hides useful distinctions.
| Edition | Features to recognize |
|---|---|
| ES2015 / ES6 | let/const, template literals, arrow functions, destructuring, default and rest parameters, array/call spread, classes, modules, promises, iterators, and generators |
| ES2017 | async/await |
| ES2018 | Object rest and spread properties |
| ES2020 | Optional chaining, such as user?.name |
The edition introductions in the ECMAScript 2020 specification document these later additions. You don't need to memorize the years; remember that “ES6 support” isn't a complete compatibility policy.
Getting Started Without a Build Step
Install a currently supported Long-Term Support (LTS) release from the official Node.js download page, using an installer or a platform-appropriate version manager. You don't need the newest Current release just to learn ES6. Our JavaScript ES6 environment setup guide covers the setup in more detail.
Each feature example below stands alone. Save one as demo.mjs and run it with Node.js; the modules section uses two explicitly named files instead.
node demo.mjsThe .mjs extension explicitly identifies a file as an ES module in Node.js. No Babel configuration or package installation is needed for these feature examples.
Essential JavaScript ES6 Features
let and const: Variables With Block Scope
Use const when you won't reassign a variable and let when you will. Both are block-scoped: a declaration inside a pair of block braces belongs to that block.
let count = 0;
const settings = { theme: "light" };
if (true) {
let count = 10;
console.log(count); // 10: a separate, block-local variable
}
count += 1;
settings.theme = "dark";
console.log(count, settings.theme); // 1 darkconst prevents reassignment, not object mutation. Changing settings.theme is allowed; assigning a different object to settings isn't. By contrast, var isn't block-scoped: inside a function, its scope is the function rather than an enclosing if block. MDN's variable scope guide explains the rules.
Template Literals: Readable Strings With Expressions
Template literals use backticks and interpolate expressions inside ${...}. They also allow literal line breaks.
const name = "World";
console.log(`Hello, ${name}!`);This prints Hello, World!. Use interpolation when it makes a string easier to read than a chain of concatenations.
Arrow Functions: Short Syntax, Different this Behavior
An arrow function can return an expression without an explicit return:
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5Arrow functions don't have their own this; they use the value from their surrounding scope. That's useful for callbacks, but makes them unsuitable replacements for methods that need a receiver-dependent this. They also cannot be constructors.
When you add a brace-delimited function body, return the result explicitly: (a, b) => { return a + b; }. For more examples, see JavaScript ES6 functions.
Destructuring: Extract Values Into Variables
Destructuring extracts object properties or iterable values into variables. Object patterns match property names; array patterns take values by position.
const user = { name: "Ada" };
const { name: userName, role = "reader" } = user;
const [first, second] = [10, 20];
console.log(userName, role); // Ada reader
console.log(first, second); // 10 20Here, name: userName renames the local variable without changing the property. The default role applies when its value is missing or undefined, not when it's null.
Default Parameters: Supply a Fallback
Default parameters apply when an argument is omitted or explicitly undefined. Values such as null, 0, and an empty string don't trigger the default.
function greet(name = "World") {
return `Hello, ${name}!`;
}
console.log(greet()); // Hello, World!
console.log(greet("Ada")); // Hello, Ada!
console.log(greet(null)); // Hello, null!Rest and Spread: Gather Values or Expand Them
A rest parameter gathers remaining arguments into an array. Spread syntax does the opposite in a call or array literal: it expands an iterable into individual values.
function total(...numbers) {
return numbers.reduce((sum, value) => sum + value, 0);
}
const prices = [10, 20];
const allPrices = [...prices, 5];
console.log(total(...allPrices)); // 35The same three dots have different jobs depending on their position. A rest parameter must come last. Array spread creates a shallow copy: nested objects remain shared references. Object spread, such as { ...user }, came later in ES2018.
Classes: Organize Objects Without Replacing Prototypes
JavaScript classes provide syntax over JavaScript's prototype-based inheritance model. A prototype is an object from which another object can inherit properties and methods; classes don't replace that model.
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
const user = new User("Ada");
console.log(user.greet()); // Hello, Ada!The constructor initializes each instance, while ordinary instance methods such as greet are shared through the prototype. Use classes when they make related state and behavior clearer—not because every object needs one. Continue with our introduction to ES6 classes.
Promises: Handle an Eventual Result or Failure
A promise represents the eventual completion or failure of an operation. Promise chains let you compose asynchronous steps and handle errors without deeply nesting callbacks.
const fetchData = (shouldFail = false) => new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(new Error("Request failed"));
return;
}
resolve("Data fetched");
}, 1000);
});
fetchData()
.then((data) => console.log(data))
.catch((error) => console.error("Could not load data:", error.message));This timer simulates an asynchronous operation; it doesn't make a network request. Call fetchData(true) instead to exercise the rejection branch.
.then() returns a new promise. Return any asynchronous work you start inside its callback so the chain can track it, and handle failures rather than ignoring rejected promises. Later async/await syntax builds on promises. Our ES6 promise chaining tutorial develops this pattern further.
Iterators and Generators: Produce Values on Demand
An iterator exposes a next() method that returns an IteratorResult object with value and done semantics, but either property may be omitted. An absent done is treated as false, and an absent value is read as undefined. A generator function, written with function*, makes it easier to implement an iterator by pausing at yield.
function* steps() {
yield "read";
yield "write";
}
for (const step of steps()) {
console.log(step);
}This prints read, then write. These iteration mechanisms concern producing and consuming sequences; an ES2015 generator isn't inherently asynchronous. Promises address eventual results, while generators address resumable execution and iteration.
ES6 Modules: A Working import/export Example
Modules give you explicit boundaries between files. Export what another file needs, then import it where you use it.
Create math.mjs:
export function add(a, b) {
return a + b;
}Create app.mjs in the same directory:
import { add } from "./math.mjs";
console.log(add(2, 3)); // 5Run the entry point:
node app.mjsIn Node.js ES modules, include file extensions in relative imports. You can alternatively use .js files and explicitly set "type": "module" in the nearest parent package.json.
In a browser, an entry point uses <script type="module" src="./app.mjs"></script>. Serve the files over HTTP with a JavaScript-compatible MIME type rather than opening the HTML through a file: URL. MDN's JavaScript modules guide covers browser loading and troubleshooting.
Do You Need Babel in 2026?
Don't add Babel merely because your source contains ES6. First identify the browsers or runtimes you support, then check compatibility for the features you actually use. Your existing build tool may already handle compilation.
| Concern | What handles it | Purpose |
|---|---|---|
| Unsupported syntax | A compiler such as Babel | Rewrite syntax into a form the target can parse |
| Missing built-in APIs | Runtime polyfills, such as core-js | Provide implementations of missing APIs |
| Bundling | A bundler such as Webpack | Follow dependencies and produce deployable bundles |
Babel is a JavaScript compiler, not a bundler or a general performance optimizer. Transforming an arrow function doesn't supply a missing Promise, Map, or Array.prototype.includes implementation; those require separate runtime compatibility decisions.
Optional: A Complete Minimal Babel Build
If you need Babel, create a separate practice project and install the matching scoped packages used by the Babel toolchain:
mkdir es6-babel-demo
cd es6-babel-demo
npm init -y
npm install --save-dev @babel/core @babel/cli @babel/preset-env
mkdir srcCreate babel.config.json in the project root:
{
"targets": "defaults",
"presets": ["@babel/preset-env"]
}This explicitly selects the Browserslist defaults query as an illustrative browser target. Replace it with your actual support policy; Babel's targets documentation also explains runtime-specific targets.
Create src/example.js:
const greet = (name = "World") => `Hello, ${name}!`;
console.log(greet());Compile and run it:
npx babel src --out-dir dist
node dist/example.jsThe Babel CLI writes the compiled files to dist. @babel/preset-env selects syntax transformations according to your targets, so unchanged modern syntax can be the correct output. This configuration doesn't add runtime polyfills.
Add a bundler only when your project needs it. Webpack processes a dependency graph and can integrate compilation, but bundling and syntax transformation remain separate concerns. Our Webpack quick-start tutorial introduces that workflow. Test the final output in your oldest supported environments, not just your development browser.
FAQ
What is the difference between ES6 and ES2015?
ES6 and ES2015 name the same edition of ECMAScript: the sixth edition, published in 2015. ES6 doesn't mean every feature available in modern JavaScript.
Do I still need Babel for ES6 features in 2026?
No, not simply because you're writing ES6. Use Babel when your supported browsers or runtimes need syntax transformations, and check built-in API support separately because syntax compilation alone doesn't add polyfills.
Is learning ES6 still relevant in 2026?
Yes. Its variables, functions, modules, classes, and promises remain foundational to modern JavaScript; learn them as everyday language features rather than as a separate language.
What other tools improve ES6 development efficiency?
Use native modules for small projects when your targets support them. Add a bundler such as Webpack when you need dependency and asset processing; add Babel only if your compatibility or compiler requirements call for it.
Has the syntax for modules changed since ES6?
The basic import/export syntax introduced in ES2015 is still in use. Module loading has evolved, so file extensions, package configuration, and browser-versus-Node.js resolution rules still matter.
Further Reading
- ECMAScript 2015 specification — the original ES6 language definition.
- MDN JavaScript modules — imports, exports, and browser loading.
- Node.js ECMAScript modules — file extensions, package configuration, and resolution.
- MDN Using promises — chaining, returned promises, and error handling.
- Babel CLI documentation — compiling files and directories.
- Babel configuration options — defining supported target environments.
- Babel preset-env documentation — target-based syntax transformations.
- Babel migration guide — configuration changes, including polyfill integration.
