WeakMap, WeakSet, and Generators
WeakMap
WeakMap is a collection of key/value pairs where the keys must be objects and the references to the objects are held weakly.
Basic WeakMap Operations
// Creating a WeakMap
const wm = new WeakMap();
// Adding entries
let obj1 = { id: 1 };
let obj2 = { id: 2 };
wm.set(obj1, 'Data for object 1');
wm.set(obj2, 'Data for object 2');
// Retrieving values
console.log(wm.get(obj1)); // 'Data for object 1'
// Checking existence
console.log(wm.has(obj1)); // true
// Deleting entries
wm.delete(obj1);
console.log(wm.has(obj1)); // false
Memory Management with WeakMap
const cache = new WeakMap();
function processObject(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = expensiveOperation(obj);
cache.set(obj, result);
return result;
}
let user = { id: 1 };
processObject(user);
// When user is set to null and no other references exist
user = null;
// The WeakMap entry is eligible for garbage collection
Private Data Pattern
const privateData = new WeakMap();
class User {
constructor(name, age) {
privateData.set(this, {
name,
age
});
}
getName() {
return privateData.get(this).name;
}
getAge() {
return privateData.get(this).age;
}
}
const user = new User('John', 30);
console.log(user.getName()); // 'John'
// privateData.get(user) is not accessible outside the class
WeakSet
WeakSet is a collection of objects stored in a set where references to the objects are held weakly.
Basic WeakSet Operations
// Creating a WeakSet
const ws = new WeakSet();
// Adding objects
let obj1 = { id: 1 };
let obj2 = { id: 2 };
ws.add(obj1);
ws.add(obj2);
// Checking membership
console.log(ws.has(obj1)); // true
// Removing objects
ws.delete(obj1);
console.log(ws.has(obj1)); // false
Use Case: Object Tagging
const processedItems = new WeakSet();
function processItem(item) {
if (processedItems.has(item)) {
console.log('Already processed');
return;
}
// Process the item
console.log('Processing item:', item);
processedItems.add(item);
}
let item = { data: 'example' };
processItem(item); // Processing item: { data: 'example' }
processItem(item); // Already processed
Generator Functions
Generators are functions that can be paused and resumed, yielding multiple values over time.
Basic Generator Syntax
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
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: undefined, done: true }
Infinite Sequence Generator
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
const numbers = infiniteSequence();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
// ...continues infinitely
Generator with Input
function* conversation() {
const name = yield "What's your name?";
const age = yield `Hello ${name}, how old are you?`;
return `${name} is ${age} years old`;
}
const conv = conversation();
console.log(conv.next().value); // What's your name?
console.log(conv.next('John').value); // Hello John, how old are you?
console.log(conv.next('30').value); // John is 30 years old
Async Generator Pattern
async function* asyncGenerator() {
let i = 0;
while (i < 3) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i++;
}
}
async function runGenerator() {
for await (const value of asyncGenerator()) {
console.log(value); // Logs 0, 1, 2 with 1-second delays
}
}
Generator Delegation
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield* generator1();
yield 3;
}
const gen = generator2();
for (const value of gen) {
console.log(value); // 1, 2, 3
}
Advanced Generator Patterns
Custom Iteration Protocol
class CustomIterable {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
const iterable = new CustomIterable();
for (const value of iterable) {
console.log(value); // 1, 2, 3
}
Error Handling in Generators
function* errorGenerator() {
try {
yield 1;
yield 2;
yield 3;
} catch (error) {
console.log('Error caught:', error);
yield 'Error handled';
}
}
const gen = errorGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw(new Error('Test error'))); // Logs error and returns { value: 'Error handled', done: false }
Generator Pipeline
function* filter(numbers, predicate) {
for (const number of numbers) {
if (predicate(number)) {
yield number;
}
}
}
function* map(numbers, transform) {
for (const number of numbers) {
yield transform(number);
}
}
// Usage
const numbers = [1, 2, 3, 4, 5];
const pipeline = map(
filter(numbers, n => n % 2 === 0),
n => n * 2
);
for (const value of pipeline) {
console.log(value); // 4, 8
}
Best Practices
- Memory Management
// Good: WeakMap for caching
const cache = new WeakMap();
function cached(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = expensive(obj);
cache.set(obj, result);
return result;
}
// Bad: Using Map for object references
const cache = new Map(); // Memory leak potential
- Generator Function Naming
// Good: Clear naming convention
function* generateUserIds() {
let id = 1;
while (true) {
yield `USER_${id++}`;
}
}
// Bad: Unclear purpose
function* gen() {
// ...
}
- Error Handling
async function* safeGenerator() {
try {
yield* riskyOperation();
} catch (error) {
yield { error: error.message };
}
}
These features provide powerful tools for:
- Memory-efficient caching (WeakMap)
- Object lifetime tracking (WeakSet)
- Lazy evaluation (Generators)
- Custom iteration protocols
- Asynchronous data streams
- Complex control flow patterns
Remember that WeakMap and WeakSet are specifically designed for memory management scenarios, while Generators excel at handling sequences and asynchronous operations.