Skip to main content

ES6 and Beyond

Status

This note is complete, reviewed, and considered stable.

Modern JavaScript (ES6+) introduced several powerful features that simplify code, improve readability, and enable developers to write more expressive and maintainable applications. Below is a detailed explanation of these features.

Template Literals​

Template literals are a cleaner way to create strings and include dynamic values. They use backticks (`) instead of quotes and allow embedding expressions with ${}.

Features:

  • Multi-line strings
  • String interpolation
  • Tagged templates

Example:

// Multi-line strings
const multiLine = `This is
a multi-line string.`;
console.log(multiLine);

// String interpolation
const name = "John";
const greeting = `Hello, ${name}!`;
console.log(greeting); // Output: Hello, John!

// Tagged templates
function tag(strings, value) {
return `${strings[0]}${value.toUpperCase()}`;
}
const message = tag`Hello, ${name}`;
console.log(message); // Output: Hello, JOHN

Destructuring​

Destructuring allows you to unpack values from arrays or objects into separate variables, making your code more concise.

Array Destructuring​

const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // Output: 1 2 3

Object Destructuring​

const person = { name: "Alice", age: 25 };
const { name, age } = person;
console.log(name, age); // Output: Alice 25

Default Values​

const { x = 10, y = 20 } = { x: 5 };
console.log(x, y); // Output: 5 20

Nested Destructuring​

const user = { id: 1, details: { name: "Bob", age: 30 } };
const {
details: { name, age },
} = user;
console.log(name, age); // Output: Bob 30

Spread and Rest Operators​

The ... operator is used for both spreading and collecting values.

Spread Operator​

  • Expands elements of arrays or objects.
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // Output: [1, 2, 3, 4]

const obj1 = { a: 1 };
const obj2 = { ...obj1, b: 2 };
console.log(obj2); // Output: { a: 1, b: 2 }

Rest Operator​

  • Collects remaining elements into an array or object.
const [first, ...rest] = [1, 2, 3, 4];
console.log(first, rest); // Output: 1 [2, 3, 4]

const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(others); // Output: { b: 2, c: 3 }

Arrow Functions and Lexical Scoping​

Arrow functions provide a concise syntax for writing functions and bind this lexically, which means they inherit this from the surrounding context.

Syntax​

const add = (a, b) => a + b;
console.log(add(2, 3)); // Output: 5

Lexical this​

function Counter() {
this.count = 0;
setInterval(() => {
this.count++;
console.log(this.count);
}, 1000);
}
new Counter(); // Inherits `this` from Counter

Modules (import/export)​

JavaScript modules allow splitting code into reusable pieces. Modules use export and import keywords.

Exporting​

// utils.js
export const add = (a, b) => a + b;
export default function greet(name) {
return `Hello, ${name}`;
}

Importing​

// main.js
import greet, { add } from "./utils.js";
console.log(greet("John")); // Output: Hello, John
console.log(add(2, 3)); // Output: 5

Internals​

  • Modules are loaded asynchronously.
  • Each module runs in its own scope, preventing global namespace pollution.
  • A module is evaluated only once, and its exports are cached.

Generator Functions and Async Iterators​

Generators are special functions that can pause execution and resume later. They are defined using the function* syntax.

Generator Functions​

function* count() {
yield 1;
yield 2;
yield 3;
}
const iterator = count();
console.log(iterator.next().value); // Output: 1
console.log(iterator.next().value); // Output: 2
console.log(iterator.next().value); // Output: 3

Async Iterators​

Used to handle asynchronous data streams with the for await...of loop.

async function* fetchData() {
yield await Promise.resolve("Data 1");
yield await Promise.resolve("Data 2");
}
(async () => {
for await (const data of fetchData()) {
console.log(data);
}
})();

Proxy and Reflect API​

The Proxy object allows you to create a proxy for an object to intercept and redefine operations like property access, assignment, and function invocation.

Proxy​

const handler = {
get(target, prop) {
return prop in target ? target[prop] : "Property not found";
},
};
const obj = { a: 1 };
const proxy = new Proxy(obj, handler);
console.log(proxy.a); // Output: 1
console.log(proxy.b); // Output: Property not found

Reflect API​

The Reflect object provides methods to perform operations on objects (like get, set, etc.) in a more predictable way.

const obj = { a: 1 };
Reflect.set(obj, "b", 2);
console.log(Reflect.get(obj, "b")); // Output: 2

Proxy with Reflect​

const handler = {
get(target, prop, receiver) {
console.log(`Accessing property "${prop}"`);
return Reflect.get(target, prop, receiver);
},
};
const obj = { name: "John" };
const proxy = new Proxy(obj, handler);
console.log(proxy.name); // Output: Accessing property "name" \n John