Free JavaScript reference · Python timeline

JavaScript ES2015 (ES6): The modern JavaScript foundation

Block-scoped variables, arrow functions, templates, destructuring, collections, promises, classes, and modules reshape everyday JavaScript.

Edition 6 · Published · Editorial review October 26, 2026

Standardized in ES2015 · Syntax

Block scope with let and const

let and const bind names within a block. They make ownership clearer than function-scoped var, especially inside loops and conditionals. A name cannot be read before its declaration has initialized it: this is the temporal dead zone.

Use let when you intend to reassign the binding. Use const when the binding should keep pointing to the same value. A constant binding does not freeze the object it references, so appending to a const array remains valid.

The inner score below belongs to a separate block. Updating it does not change the outer score. Prefer names that describe separate roles even though shadowing is legal.

Worked example

Uses ES2015 features · Script

let score = 2;
{
  let score = 5;
  console.log(score);
}
const labels = ["start"];
labels.push("finish");
console.log(score, labels.length);

Expected output

5
2 2

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

const prevents reassignment, not mutation. Moving a let declaration downward can also introduce a ReferenceError rather than the undefined value familiar from var.

Try it yourself

Try assigning labels = [] and explain the error. Then replace the inner let with an assignment and predict the outer score.

Read more: ES2015 specification · MDN explanation and compatibility

Standardized in ES2015 · Syntax

Arrow functions capture this

Arrow functions are concise function expressions. An expression body returns its value implicitly; a brace body needs an explicit return. Parenthesize an object literal when returning it from an expression body.

An arrow captures this from the enclosing scope rather than receiving it from the call site. That is useful for callbacks that should retain an enclosing method's receiver. Ordinary methods are still useful when the caller should determine the receiver.

The example creates the callback inside a normal method, where this already refers to the record. Arrows are not constructors and do not have their own arguments binding.

Worked example

Uses ES2015 features · Script

const record = {
  prefix: "ID",
  format(values) {
    return values.map(value => `${this.prefix}:${value}`);
  }
};
console.log(record.format([2, 3]).join(", "));
const make = value => ({value});
console.log(make(4).value);

Expected output

ID:2, ID:3
4

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

Using an arrow as a method does not give it the object as this. A brace body without return silently returns undefined.

Try it yourself

Change make to value => {value} and predict its result. Why does the format method use normal method syntax?

Read more: ES2015 specification · MDN explanation and compatibility

Standardized in ES2015 · Syntax

Template literals and interpolation

Backticks create template literals. ${...} evaluates an expression and inserts its string form. This keeps labels beside their values and avoids fragile chains of quote and plus operators.

Line breaks inside an untagged template become part of the resulting string. Formatting still needs an explicit choice: calling toFixed(2) produces display text, while the original number remains available for arithmetic.

Interpolation is not HTML escaping. Use a destination-appropriate API or escaping strategy when inserting user-controlled values into markup.

Related context

const label = item + ": $" + price.toFixed(2);

Context fragment; read it with the worked example.

Worked example

Uses ES2015 features · Script

const item = "Notebook";
const price = 4.5;
console.log(`${item}: $${price.toFixed(2)}`);
console.log(`total: ${2 + 3}`);

Expected output

Notebook: $4.50
total: 5

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

Single or double quotes do not interpolate ${...}. Interpolated text is not automatically safe HTML or SQL.

Try it yourself

Build a two-line label and inspect it with JSON.stringify. Which newline becomes part of the stored string?

Read more: ES2015 specification · MDN explanation and compatibility

Standardized in ES2015 · Syntax

Destructuring, defaults, rest, and array spread

Destructuring extracts selected parts of arrays or objects into names. A default applies when the extracted value is undefined; it does not replace null, zero, or an empty string.

A rest parameter collects remaining function arguments into an array. Array spread expands an iterable into elements. The dots look similar, but the surrounding position determines whether they collect or expand.

These are shallow operations: spreading an array copies its outer sequence while nested objects remain shared. Object rest/spread arrived later in ES2018 and is covered in that edition.

Worked example

Uses ES2015 features · Script

const {name = "guest", score = 0} = {name: "Ada"};
const [first, ...remaining] = [2, 3, 4];
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
console.log(name, score);
console.log(first, sum(...remaining));

Expected output

Ada 0
2 7

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

A default does not replace an explicitly supplied null. Do not mistake a shallow copy for independent nested data.

Try it yourself

Supply score: null and predict the extracted score. Then add an empty array to test the rest-parameter sum.

Read more: ES2015 specification · MDN explanation and compatibility

Standardized in ES2015 · Built-in collection

Map keys and Set membership

A Map associates keys with values without converting every key to a property-name string. A Set stores unique values. Both preserve insertion order when iterated, which is useful for predictable reports and deduplication.

Object keys are compared by identity rather than by matching their fields. Two fresh objects with the same-looking contents are distinct keys. Collections use SameValueZero equality for primitives, so Set can deduplicate NaN as well.

The example uses strings for counts and deduplicates a small sequence. Choose a normal object when named record fields are the actual model; choose Map when a general collection of keys is the model.

Worked example

Uses ES2015 features · Script

const counts = new Map();
counts.set("Ada", 2);
counts.set("Ada", counts.get("Ada") + 1);
console.log(counts.get("Ada"), counts.size);
const unique = new Set([2, 2, 3]);
console.log(JSON.stringify([...unique]));

Expected output

3 1
[2,3]

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

map[key] writes an ordinary property, not a Map entry. Use set/get/has and test presence separately when undefined is a legitimate value.

Try it yourself

Use two separate {id: 1} objects as Map keys. Predict the size and explain the identity rule.

Read more: ES2015 specification · MDN explanation and compatibility

Standardized in ES2015 · Built-in async API

Promises compose eventual results

A Promise represents an eventual value or failure. Calling .then creates another promise, so returning a transformed value lets the next step consume it. Throwing inside a handler rejects the resulting promise.

Handlers run after the current synchronous work, even for an already fulfilled promise. This makes their ordering different from directly calling an ordinary function. The example saves the final promise as finished so a surrounding test can await it.

Use catch for failures you can handle. A promise does not imply a background thread, cancellation, or a guarantee that the work succeeds.

Worked example

Uses ES2015 features · Script

const finished = Promise.resolve(3)
  .then(value => value * 2)
  .then(value => console.log(value));
console.log("scheduled");

Expected output

scheduled
6

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

Forgetting to return a promise from a then handler can make later steps finish too early. An unobserved rejection is not handled merely because the work is asynchronous.

Try it yourself

Insert a handler that throws and a catch that prints its message. Predict which later success handlers are skipped.

Read more: ES2015 specification · MDN explanation and compatibility

Standardized in ES2015 · Syntax

Class syntax over prototypes

Class declarations provide a structured way to define a constructor and prototype methods. Creating an instance with new runs the constructor; methods can then work with that instance through this.

Classes still use JavaScript's prototype model. A method is not automatically bound to its instance when it is passed elsewhere as a callback. Calling an extracted method can therefore lose the receiver.

The example keeps calls attached to the instance. Public fields and hash-prefixed private fields are later additions, covered in ES2022.

Worked example

Uses ES2015 features · Script

class Counter {
  constructor(start) { this.value = start; }
  increment() { this.value += 1; }
}
const counter = new Counter(2);
counter.increment();
console.log(counter.value);

Expected output

3

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

Class syntax does not automatically bind this or make a property private. A leading underscore is a convention rather than access control.

Try it yourself

Create a second Counter and verify the two instances keep separate values. What changes if increment is passed as an unbound callback?

Read more: ES2015 specification · MDN explanation and compatibility

Standardized in ES2015 · Modules

Modules declare exports explicitly

A JavaScript module declares the values it exports, and other modules import those values. Module bindings avoid putting every helper into a shared global namespace. Modules use strict mode and have their own scope.

Save the worked example as numbers.mjs for Node.js, or load it as a browser module. A consumer could import its named export from the appropriate URL or file path. Resolution rules and file loading belong partly to the host environment.

The example is a complete module and can execute on its own. The consumer line is a context fragment that requires the corresponding file; it is not a standalone script.

Related context

// Consumer fragment; numbers.mjs must exist.
import {double} from "./numbers.mjs";

Context fragment; read it with the worked example.

Worked example

Uses ES2015 features · ES module

export function double(value) {
  return value * 2;
}
console.log(double(3));

Expected output

6

Run as an ES module, not as a function pasted into the practice editor.

Example checked with Chromium 151.0.7922.34, Node.js 24.19.0.

Common mistake

export is not valid in a classic script. Module features do not make browser DOM APIs available in Node.js or in a worker.

Try it yourself

Add a named constant export, then write the import a separate consumer would need. Explain which relative path depends on your file layout.

Read more: ES2015 specification · MDN explanation and compatibility

Recap

Block-scoped variables, arrow functions, templates, destructuring, collections, promises, classes, and modules reshape everyday JavaScript. Check the engines your project supports before adopting new syntax or built-ins.

Read the ES2015 specification →