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.