16. Advanced Topics

Metaprogramming

Symbols

// Creating symbols
const sym1 = Symbol('description');
const sym2 = Symbol('description');

console.log(sym1 === sym2); // false (unique)

// Global symbols
const globalSym = Symbol.for('shared');
const sameSym = Symbol.for('shared');
console.log(globalSym === sameSym); // true

// Well-known symbols
const obj = {
    [Symbol.iterator]: function* () {
        yield 1;
        yield 2;
        yield 3;
    }
};

for (const value of obj) {
    console.log(value); // 1, 2, 3
}

Reflect API

const obj = { a: 1, b: 2 };

// Get property
console.log(Reflect.get(obj, 'a')); // 1

// Set property
Reflect.set(obj, 'c', 3);
console.log(obj.c); // 3

// Check if property exists
console.log(Reflect.has(obj, 'a')); // true

// Delete property
Reflect.deleteProperty(obj, 'b');
console.log(obj); // { a: 1, c: 3 }

// Get own keys
console.log(Reflect.ownKeys(obj)); // ['a', 'c']

// Construct objects
class Person {
    constructor(name) {
        this.name = name;
    }
}

const person = Reflect.construct(Person, ['John']);
console.log(person.name); // 'John'

Proxy

const target = {
    name: 'John',
    age: 30
};

const handler = {
    get(target, property) {
        console.log(`Getting ${property}`);
        return target[property];
    },

    set(target, property, value) {
        console.log(`Setting ${property} to ${value}`);
        target[property] = value;
        return true;
    }
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // "Getting name" then "John"
proxy.age = 31;          // "Setting age to 31"

Iterators and Generators

Custom Iterator

class Range {
    constructor(start, end) {
        this.start = start;
        this.end = end;
    }

    [Symbol.iterator]() {
        let current = this.start;
        const end = this.end;

        return {
            next() {
                if (current <= end) {
                    return { value: current++, done: false };
                } else {
                    return { done: true };
                }
            }
        };
    }
}

const range = new Range(1, 5);
for (const num of range) {
    console.log(num); // 1, 2, 3, 4, 5
}

Generator Functions

function* fibonacci() {
    let [prev, curr] = [0, 1];
    while (true) {
        yield curr;
        [prev, curr] = [curr, prev + curr];
    }
}

const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5

Async Generators

async function* asyncDataStream() {
    const data = [1, 2, 3, 4, 5];

    for (const item of data) {
        // Simulate async operation
        await new Promise(resolve => setTimeout(resolve, 100));
        yield item * 2;
    }
}

async function consumeAsyncGenerator() {
    for await (const value of asyncDataStream()) {
        console.log(value); // 2, 4, 6, 8, 10 (with delays)
    }
}

consumeAsyncGenerator();

Iterator Helpers (ES2025)

// Create an iterator from any iterable and compose lazily
const result = Iterator.from([1, 2, 3, 4, 5])
    .map(x => x * 2)
    .filter(x => x > 5)
    .take(3)
    .toArray();

console.log(result); // [6, 8, 10]

Decorators (Stage 3 Proposal)

Class Decorators

function logged(constructor) {
    const original = constructor;

    function construct(constructor, args) {
        console.log(`Creating instance of ${constructor.name}`);
        return new constructor(...args);
    }

    const newConstructor = function(...args) {
        return construct(original, args);
    };

    newConstructor.prototype = original.prototype;
    return newConstructor;
}

@logged
class Person {
    constructor(name) {
        this.name = name;
    }
}

const person = new Person('John'); // "Creating instance of Person"

Method Decorators

function readonly(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

class Person {
    constructor(name) {
        this.name = name;
    }

    @readonly
    getName() {
        return this.name;
    }
}

const person = new Person('John');
person.getName = () => 'Modified'; // Error in strict mode

WeakRefs and FinalizationRegistry (ES12+)

WeakRef

let obj = { data: 'important' };
const weakRef = new WeakRef(obj);

console.log(weakRef.deref()); // { data: 'important' }

obj = null; // Remove strong reference

// The object may be garbage collected
setTimeout(() => {
    const ref = weakRef.deref();
    if (ref) {
        console.log('Object still exists:', ref);
    } else {
        console.log('Object was garbage collected');
    }
}, 1000);

FinalizationRegistry

const registry = new FinalizationRegistry((heldValue) => {
    console.log(`Object with value ${heldValue} was garbage collected`);
});

let obj = { name: 'test' };
registry.register(obj, 'some value');

obj = null; // Object can be garbage collected
// When garbage collected, the callback will be called with 'some value'

Internationalization

Intl Object

// Number formatting
const number = 123456.789;

console.log(new Intl.NumberFormat('en-US').format(number)); // "123,456.789"
console.log(new Intl.NumberFormat('de-DE').format(number)); // "123.456,789"
console.log(new Intl.NumberFormat('en-IN').format(number)); // "1,23,456.789"

// Currency formatting
console.log(new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
}).format(1234.56)); // "$1,234.56"

// Date formatting
const date = new Date();

console.log(new Intl.DateTimeFormat('en-US').format(date)); // "12/25/2023"
console.log(new Intl.DateTimeFormat('ko-KR').format(date)); // "2023. 12. 25."

// Collator for string comparison
const collator = new Intl.Collator('en');
console.log(['Z', 'a', 'z', 'ä'].sort(collator.compare)); // ['a', 'ä', 'z', 'Z']

Regular Expressions Updates

RegExp v flag and set notation (ES2024)

// Digits from any script except ASCII digits
const re = /[\p{Decimal_Number}--[0-9]]/v;
console.log(re.test('٥')); // true (Arabic-Indic five)
console.log(re.test('5')); // false

RegExp modifiers (ES2025)

// Scoped case-insensitive matching
const m = /^(?i:ab)c$/.test('ABc');
console.log(m); // true

Duplicate named capture groups (ES2025)

// Same name allowed in different alternatives; the participating one wins
const r = /(?:(?<word>\w+)-|(?<word>\w+)_)/;
console.log(r.exec('foo-').groups.word); // "foo"
console.log(r.exec('bar_').groups.word); // "bar"

RegExp.escape (ES2025)

const userInput = '(*.*) + [a-z]';
const safe = RegExp.escape(userInput);
const rx = new RegExp(safe);
console.log(rx.test(userInput)); // true

Performance Optimization

Memoization

function memoize(fn) {
    const cache = new Map();

    return function(...args) {
        const key = JSON.stringify(args);

        if (cache.has(key)) {
            return cache.get(key);
        }

        const result = fn.apply(this, args);
        cache.set(key, result);
        return result;
    };
}

const expensiveFunction = (n) => {
    console.log(`Computing for ${n}`);
    return n * n;
};

const memoizedFunction = memoize(expensiveFunction);

console.log(memoizedFunction(5)); // "Computing for 5" then 25
console.log(memoizedFunction(5)); // 25 (cached)

Debouncing and Throttling

// Debounce: delays execution until after delay has passed
function debounce(func, delay) {
    let timeoutId;

    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
}

// Throttle: ensures function executes at most once per interval
function throttle(func, interval) {
    let lastCallTime = 0;

    return function(...args) {
        const now = Date.now();

        if (now - lastCallTime >= interval) {
            lastCallTime = now;
            func.apply(this, args);
        }
    };
}

// Usage
const debouncedSearch = debounce((query) => {
    console.log(`Searching for: ${query}`);
}, 300);

const throttledScroll = throttle(() => {
    console.log('Scroll event');
}, 100);

Memory Management

Garbage Collection

// Creating objects
let obj = { data: 'some data' };

// Creating circular references
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;

// Breaking references to allow GC
obj1.ref = null;
obj2.ref = null;

// Using WeakMap to avoid memory leaks
const cache = new WeakMap();

function getData(key) {
    if (cache.has(key)) {
        return cache.get(key);
    }

    const data = expensiveComputation(key);
    cache.set(key, data);
    return data;
}

Web APIs Integration

Fetch API

async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        clearTimeout(timeoutId);

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        return await response.json();
    } catch (error) {
        if (error.name === 'AbortError') {
            throw new Error('Request timed out');
        }
        throw error;
    }
}

Web Workers

// main.js
const worker = new Worker('worker.js');

worker.postMessage({ command: 'start', data: [1, 2, 3, 4, 5] });

worker.onmessage = function(e) {
    console.log('Result from worker:', e.data);
};

// worker.js
self.onmessage = function(e) {
    const { command, data } = e.data;

    if (command === 'start') {
        const result = data.reduce((sum, num) => sum + num, 0);
        self.postMessage(result);
    }
};

Next Steps

These advanced topics provide deep insights into ECMAScript's capabilities. Next, let's explore best practices for writing maintainable code.