ES6 and Beyond
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