Chapter 14: Modern JavaScript Features (ES6+)
Destructuring with Default Values and Renaming
// Complex destructuring scenarios const config = { api: { baseUrl: "https://api.example.com", timeout: 5000 }, features: { darkMode: true } }; // Nested destructuring with defaults and renaming const { api: { baseUrl: apiUrl = "https://default.com", timeout = 3000, retries = 3 // Not in original object }, features: { darkMode = false, notifications: enableNotifications = true } = {} // Default to empty object if features is undefined } = config; console.log(apiUrl, timeout, retries, darkMode, enableNotifications);
Advanced Array Methods
const users = [ { id: 1, name: "Alice", age: 25, active: true }, { id: 2, name: "Bob", age: 30, active: false }, { id: 3, name: "Charlie", age: 35, active: true }, { id: 4, name: "Diana", age: 28, active: true } ]; // Chaining array methods const result = users .filter(user => user.active) // Only active users .map(user => ({ ...user, ageGroup: user.age < 30 ? 'young' : 'mature' })) .sort((a, b) => a.age - b.age) // Sort by age .reduce((acc, user) => { // Group by age group const group = user.ageGroup; acc[group] = acc[group] || []; acc[group].push(user); return acc; }, {}); console.log(result); // flatMap - map and flatten in one step const nested = [[1, 2], [3, 4], [5, 6]]; const flattened = nested.flatMap(arr => arr.map(x => x * 2)); console.log(flattened); // [2, 4, 6, 8, 10, 12] // Array.from with mapping function const range = Array.from({ length: 5 }, (_, i) => i * 2); console.log(range); // [0, 2, 4, 6, 8]
Proxy and Reflect
// Proxy - intercept and customize operations const user = { name: "Alice", age: 30 }; const userProxy = new Proxy(user, { get(target, property) { console.log(`Getting ${property}`); return target[property]; }, set(target, property, value) { console.log(`Setting ${property} to ${value}`); if (property === 'age' && value < 0) { throw new Error("Age cannot be negative"); } target[property] = value; return true; }, has(target, property) { console.log(`Checking if ${property} exists`); return property in target; } }); console.log(userProxy.name); // "Getting name" → "Alice" userProxy.age = 31; // "Setting age to 31" console.log('name' in userProxy); // "Checking if name exists" → true // Reflect - programmatic object operations const obj = { a: 1, b: 2 }; console.log(Reflect.has(obj, 'a')); // true console.log(Reflect.ownKeys(obj)); // ["a", "b"] Reflect.set(obj, 'c', 3); console.log(obj); // { a: 1, b: 2, c: 3 }
WeakMap and WeakSet
// WeakMap - weak references to objects as keys const privateData = new WeakMap(); class BankAccount { constructor(balance) { // Store private data using WeakMap privateData.set(this, { balance, transactions: [] }); } deposit(amount) { const data = privateData.get(this); data.balance += amount; data.transactions.push(`Deposited ${amount}`); return data.balance; } getBalance() { return privateData.get(this).balance; } getTransactions() { return privateData.get(this).transactions.slice(); // Return copy } } const account = new BankAccount(100); console.log(account.getBalance()); // 100 account.deposit(50); console.log(account.getBalance()); // 150 // Private data is truly private - no way to access it directly console.log(account.balance); // undefined // WeakSet - weak references to objects const visitedNodes = new WeakSet(); function traverse(node) { if (visitedNodes.has(node)) { return; // Avoid cycles } visitedNodes.add(node); // Process node... if (node.children) { node.children.forEach(child => traverse(child)); } }
Optional Chaining and Nullish Coalescing
// Optional chaining (?.) - safely access nested properties const user = { profile: { social: { twitter: "@alice" } } }; // Traditional way (verbose and error-prone) const twitter1 = user && user.profile && user.profile.social && user.profile.social.twitter; // Optional chaining (clean and safe) const twitter2 = user?.profile?.social?.twitter; const instagram = user?.profile?.social?.instagram; // undefined (no error) // Works with arrays and function calls too const firstHobby = user?.hobbies?.[0]; const result = user?.getName?.(); // Nullish coalescing (??) - default values for null/undefined const username = user?.name ?? "Anonymous"; const theme = user?.preferences?.theme ?? "light"; // Different from || operator console.log("" || "default"); // "default" (empty string is falsy) console.log("" ?? "default"); // "" (empty string is not nullish) console.log(null ?? "default"); // "default" console.log(undefined ?? "default"); // "default"
Dynamic Imports and Top-level Await
// Dynamic imports - load modules conditionally async function loadModule(condition) { if (condition) { const module = await import('./heavy-module.js'); return module.default(); } } // Top-level await (in modules) // utils.js const data = await fetch('https://api.example.com/data').then(r => r.json()); export { data }; // Code splitting with dynamic imports const LazyComponent = lazy(() => import('./LazyComponent'));
Logical Assignment Operators
// Logical assignment operators (ES2021) let a = null; let b = 0; let c = ""; // Nullish coalescing assignment a ??= "default"; // Only assigns if a is null or undefined console.log(a); // "default" // Logical AND assignment b &&= 42; // Only assigns if b is truthy console.log(b); // 0 (not assigned because 0 is falsy) // Logical OR assignment c ||= "fallback"; // Only assigns if c is falsy console.log(c); // "fallback" // Practical example class Config { constructor(options = {}) { this.debug = options.debug ?? false; this.timeout = options.timeout ?? 5000; this.retries = options.retries ?? 3; // Set default headers this.headers ??= {}; this.headers['Content-Type'] ??= 'application/json'; // Enable caching if not explicitly disabled this.cache &&= true; } }
String Methods and Pattern Matching
// String padding const num = "42"; console.log(num.padStart(5, "0")); // "00042" console.log(num.padEnd(5, "!")); // "42!!!" // String matching const text = "Hello World JavaScript"; // includes, startsWith, endsWith console.log(text.includes("World")); // true console.log(text.startsWith("Hello")); // true console.log(text.endsWith("Script")); // true // replaceAll (ES2021) const sentence = "The quick brown fox jumps over the lazy dog"; console.log(sentence.replaceAll("the", "THE")); // Replaces all occurrences // matchAll for global regex matches const regex = /\b\w{4}\b/g; // 4-letter words const matches = [...sentence.matchAll(regex)]; console.log(matches.map(match => match[0])); // ["over", "lazy"]
BigInt for Large Numbers
// BigInt for numbers larger than Number.MAX_SAFE_INTEGER const bigNumber = 9007199254740991n; // Note the 'n' suffix const anotherBig = BigInt("9007199254740992"); console.log(bigNumber + 1n); // 9007199254740992n console.log(bigNumber * 2n); // 18014398509481982n // Can't mix BigInt with regular numbers // console.log(bigNumber + 1); // TypeError! // Convert between BigInt and Number console.log(Number(bigNumber)); // May lose precision console.log(BigInt(123)); // 123n // Use cases: cryptography, precise calculations function factorial(n) { if (n <= 1n) return 1n; return n * factorial(n - 1n); } console.log(factorial(20n)); // 2432902008176640000n
Private Class Fields
// Private fields and methods (ES2022) class ModernUser { // Private fields #id; #password; // Private static field static #instanceCount = 0; constructor(username, password) { this.username = username; this.#password = this.#hashPassword(password); this.#id = ++ModernUser.#instanceCount; } // Private method #hashPassword(password) { return `hashed_${password}`; } // Public method using private fields authenticate(password) { return this.#hashPassword(password) === this.#password; } getId() { return this.#id; } // Static method using private static field static getTotalUsers() { return ModernUser.#instanceCount; } } const user = new ModernUser("alice", "secret123"); console.log(user.authenticate("secret123")); // true console.log(user.getId()); // 1 // console.log(user.#id); // SyntaxError: Private field '#id' must be declared in an enclosing class
Numeric Separators and Other Literals
// Numeric separators for readability const million = 1_000_000; const binary = 0b1010_0001; const octal = 0o755_000; const hex = 0xFF_EC_DE_5E; console.log(million); // 1000000 // Template literal improvements const createQuery = (table, fields) => ` SELECT ${fields.join(', ')} FROM ${table} WHERE active = 1 ORDER BY created_at DESC `; const query = createQuery('users', ['id', 'name', 'email']); console.log(query);
Array.at() Method
// Array.at() for easier access to elements from the end const arr = [1, 2, 3, 4, 5]; // Traditional way console.log(arr[arr.length - 1]); // 5 (last element) console.log(arr[arr.length - 2]); // 4 (second to last) // With Array.at() console.log(arr.at(-1)); // 5 (last element) console.log(arr.at(-2)); // 4 (second to last) console.log(arr.at(0)); // 1 (first element) // Works with strings too const str = "hello"; console.log(str.at(-1)); // "o"
Object.hasOwn()
// Object.hasOwn() - safer alternative to hasOwnProperty const obj = { name: "Alice", age: 30 }; // Traditional way (can be problematic) console.log(obj.hasOwnProperty("name")); // true // What if hasOwnProperty is overridden? obj.hasOwnProperty = () => false; console.log(obj.hasOwnProperty("name")); // false (wrong!) // Object.hasOwn() is safer console.log(Object.hasOwn(obj, "name")); // true (correct) // Works even with null prototype objects const nullObj = Object.create(null); nullObj.prop = "value"; // nullObj.hasOwnProperty("prop"); // TypeError! console.log(Object.hasOwn(nullObj, "prop")); // true
Performance Considerations with Modern Features
// Optional chaining has performance cost // ❌ Overuse can be expensive function processUser(user) { return { name: user?.profile?.personal?.name, email: user?.profile?.contact?.email, phone: user?.profile?.contact?.phone, address: user?.profile?.contact?.address?.street }; } // ✅ Better approach function processUserOptimized(user) { const profile = user?.profile; if (!profile) return null; const personal = profile.personal; const contact = profile.contact; return { name: personal?.name, email: contact?.email, phone: contact?.phone, address: contact?.address?.street }; } // Proxy performance consideration // ❌ Proxy adds overhead const expensiveProxy = new Proxy({}, { get(target, prop) { console.log(`Accessing ${prop}`); return target[prop]; } }); // ✅ Use Proxy judiciously for specific use cases
Next Chapter: Chapter 15: Performance and Best Practices
Previous Chapter: Chapter 13: The Interview Gauntlet - Common Tricks and Gotchas
Table of Contents: JavaScript Guide
