Back to Articles

Chapter 12: Advanced Concepts and Modern Features

July 27, 202516 min read
javascriptes6destructuringmodulessymbolsgeneratorsiteratorsadvanced
Chapter 12: Advanced Concepts and Modern Features

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