Chapter 12: Advanced Concepts and Modern Features
· 16 min read
Chapter 12: Advanced Concepts and Modern Features
Destructuring Deep Dive
// Advanced array destructuring
const matrix = [[1, 2], [3, 4], [5, 6]];
const [[a, b], [c, d]] = matrix;
console.log(a, b, c, d); // 1 2 3 4
// Swapping variables
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
// Function parameter destructuring
function processUser({ name, age, email = "No email" }) {
console.log(`${name} (${age}) - ${email}`);
}
processUser({ name: "Alice", age: 30 }); // Alice (30) - No email
// Mixed destructuring
const response = {
data: {
users: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]
},
status: 200
};
const { data: { users: [firstUser, secondUser] }, status } = response;
console.log(firstUser.name, status); // Alice 200
Template Literals and Tagged Templates
// Basic template literals
const name = "Alice";
const age = 30;
const message = `Hello, my name is ${name} and I'm ${age} years old.`;
// Multi-line strings
const html = `
<div class="user">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// Tagged template literals
function highlight(strings, ...values) {
return strings.reduce((result, string, i) => {
const value = values[i] ? `<mark>${values[i]}</mark>` : '';
return result + string + value;
}, '');
}
const highlighted = highlight`My name is ${name} and I'm ${age} years old.`;
console.log(highlighted); // My name is <mark>Alice</mark> and I'm <mark>30</mark> years old.
Modules (ES6)
// math.js - exporting
export const PI = 3.14159;
export const E = 2.71828;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// Default export
export default function subtract(a, b) {
return a - b;
}
// main.js - importing
import subtract, { PI, add, multiply as mult } from './math.js';
import * as math from './math.js';
console.log(add(5, 3)); // 8
console.log(mult(5, 3)); // 15
console.log(subtract(5, 3)); // 2
console.log(math.PI); // 3.14159
Symbols and Iterators
// Symbols for unique object keys
const SECRET_PROP = Symbol('secret');
const user = {
name: "Alice",
[SECRET_PROP]: "Hidden value"
};
console.log(user[SECRET_PROP]); // "Hidden value"
console.log(Object.keys(user)); // ["name"] - symbol keys are hidden
// Well-known symbols
const customIterable = {
data: [1, 2, 3, 4, 5],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
// Now it's iterable!
for (const value of customIterable) {
console.log(value); // 1, 2, 3, 4, 5
}
Generators
// Generator functions
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
return "done";
}
const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: "done", done: true }
// Infinite generator
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
// Generator with parameters
function* paramGenerator() {
const x = yield "Give me a number";
const y = yield `You gave me ${x}, give me another`;
return `Sum is ${x + y}`;
}
const paramGen = paramGenerator();
console.log(paramGen.next()); // { value: "Give me a number", done: false }
console.log(paramGen.next(5)); // { value: "You gave me 5, give me another", done: false }
console.log(paramGen.next(10)); // { value: "Sum is 15", done: true }
Advanced Symbol Usage
Custom Object Behavior with Symbols
// Symbol.toPrimitive - customize type conversion
const temperatureReading = {
celsius: 25,
[Symbol.toPrimitive](hint) {
switch(hint) {
case 'number':
return this.celsius;
case 'string':
return `${this.celsius}°C`;
default:
return this.celsius;
}
}
};
console.log(+temperatureReading); // 25
console.log(`Temperature: ${temperatureReading}`); // "Temperature: 25°C"
console.log(temperatureReading + 10); // 35
// Symbol.toStringTag - customize Object.prototype.toString
class CustomCollection {
get [Symbol.toStringTag]() {
return 'MyCustomCollection';
}
}
const collection = new CustomCollection();
console.log(Object.prototype.toString.call(collection)); // "[object MyCustomCollection]"
// Symbol.species - control derived object construction
class MyArray extends Array {
static get [Symbol.species]() {
return Array; // Derived objects will be Arrays, not MyArrays
}
}
const myArr = new MyArray(1, 2, 3);
const mapped = myArr.map(x => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
Advanced Generator Patterns
Generator Delegation
function* gen1() {
yield 1;
yield 2;
}
function* gen2() {
yield 3;
yield 4;
}
function* combinedGen() {
yield* gen1(); // Delegate to gen1
yield* gen2(); // Delegate to gen2
yield 5; // Own yield
}
for (const value of combinedGen()) {
console.log(value); // 1, 2, 3, 4, 5
}
// Two-way communication with generators
function* twoWayGenerator() {
const name = yield "What's your name?";
const age = yield `Hello ${name}, how old are you?`;
yield `${name} is ${age} years old.`;
}
const conversation = twoWayGenerator();
console.log(conversation.next().value); // "What's your name?"
console.log(conversation.next("Alice").value); // "Hello Alice, how old are you?"
console.log(conversation.next(30).value); // "Alice is 30 years old."
Async Generators
// Combining async/await with generators
async function* asyncDataGenerator() {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
yield data;
}
}
// Consuming async generator
async function processAsyncData() {
for await (const data of asyncDataGenerator()) {
console.log('Received data:', data);
}
}
Meta-programming with Proxy and Reflect
Advanced Proxy Patterns
// Validation proxy
function createValidatedObject(target, validators) {
return new Proxy(target, {
set(obj, prop, value) {
if (validators[prop]) {
if (!validators[prop](value)) {
throw new Error(`Invalid value for ${prop}: ${value}`);
}
}
return Reflect.set(obj, prop, value);
}
});
}
const userValidators = {
age: value => typeof value === 'number' && value >= 0 && value <= 150,
email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
name: value => typeof value === 'string' && value.length > 0
};
const user = createValidatedObject({}, userValidators);
user.name = "Alice"; // OK
user.age = 30; // OK
// user.age = -5; // Error!
// user.email = "bad"; // Error!
// Observable pattern with Proxy
function observable(target, onChange) {
return new Proxy(target, {
set(obj, prop, value) {
const oldValue = obj[prop];
const result = Reflect.set(obj, prop, value);
if (oldValue !== value) {
onChange(prop, oldValue, value);
}
return result;
},
deleteProperty(obj, prop) {
const oldValue = obj[prop];
const result = Reflect.deleteProperty(obj, prop);
onChange(prop, oldValue, undefined);
return result;
}
});
}
const state = observable({ count: 0 }, (prop, oldVal, newVal) => {
console.log(`${prop} changed from ${oldVal} to ${newVal}`);
});
state.count = 1; // "count changed from 0 to 1"
state.name = "Test"; // "name changed from undefined to Test"
delete state.name; // "name changed from Test to undefined"
Advanced Module Patterns
Dynamic Module Loading
// Conditional module loading
async function loadFeature(featureName) {
switch(featureName) {
case 'charts':
const { ChartModule } = await import('./charts.js');
return new ChartModule();
case 'maps':
const { MapModule } = await import('./maps.js');
return new MapModule();
default:
throw new Error(`Unknown feature: ${featureName}`);
}
}
// Module namespace pattern
// utils.js
export * as arrays from './array-utils.js';
export * as strings from './string-utils.js';
export * as dates from './date-utils.js';
// main.js
import * as utils from './utils.js';
console.log(utils.arrays.unique([1, 2, 2, 3]));
console.log(utils.strings.capitalize('hello'));
Advanced Destructuring Patterns
// Recursive destructuring
const nestedData = {
user: {
profile: {
personal: {
name: 'Alice',
age: 30
},
professional: {
title: 'Developer',
experience: 5
}
}
}
};
// Deep destructuring with renaming and defaults
const {
user: {
profile: {
personal: { name: userName = 'Anonymous' },
professional: { title: jobTitle = 'Unknown' }
}
}
} = nestedData;
console.log(userName, jobTitle); // Alice Developer
// Function parameter destructuring with rest
function processData({
required,
optional = 'default',
...rest
}) {
console.log(required, optional, rest);
}
processData({
required: 'value',
extra1: 'data1',
extra2: 'data2'
}); // value default { extra1: 'data1', extra2: 'data2' }
// Array destructuring with object matching
const users = [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'user' },
{ id: 3, name: 'Charlie', role: 'user' }
];
const [admin, ...regularUsers] = users.filter(u => u.role === 'admin')
.concat(users.filter(u => u.role !== 'admin'));
console.log(admin); // { id: 1, name: 'Alice', role: 'admin' }
console.log(regularUsers); // [{ id: 2, ... }, { id: 3, ... }]
Performance and Memory Considerations
// Efficient string concatenation with template literals
const parts = ['Hello', 'World', 'from', 'JavaScript'];
// ❌ Inefficient
let result1 = '';
for (const part of parts) {
result1 += part + ' ';
}
// ✅ Efficient
const result2 = parts.join(' ');
const result3 = `${parts.join(' ')}!`;
// Symbol registry for memory efficiency
const sym1 = Symbol.for('app.user.id'); // Global symbol
const sym2 = Symbol.for('app.user.id'); // Same symbol
console.log(sym1 === sym2); // true
// WeakMap for memory-efficient caching
const cache = new WeakMap();
function expensiveOperation(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = {
// Expensive calculation
computed: Object.keys(obj).length * 100
};
cache.set(obj, result);
return result;
}
Next Chapter: Chapter 13: The Interview Gauntlet - Common Tricks and Gotchas
Previous Chapter: Chapter 11: The Prototype and Inheritance Saga
Table of Contents: JavaScript Guide