WeakMap (The Invisible Notebook)
The Invisible Storage Mystery
You've seen this pattern in the reactive code:
const reactiveMap = new WeakMap();
function state(data) {
const proxy = new Proxy(data, { /* ... */ });
// Store secret data about this proxy
reactiveMap.set(proxy, {
deps: new Map(),
computedMap: new Map()
});
return proxy;
}Questions that might pop up:
- Why not just
proxy.deps = new Map()? - What's this WeakMap thing?
- Why is it "weak"?
- Where does this data go?
Let's uncover the mystery! 🔍
What is a WeakMap? (No Jargon)
Simple Definition
A WeakMap is like a secret notebook where you can attach hidden notes to objects without anyone seeing them.
Think of it as invisible sticky notes that you can put on objects. The object doesn't know the note is there, but you can always look it up later.
The Key Point
With a WeakMap, you can store metadata (extra information) about objects without modifying the objects themselves.
// Instead of this (modifying the object):
user.__secretData = { tracked: true, visits: 5 };
// You do this (invisible storage):
const secrets = new WeakMap();
secrets.set(user, { tracked: true, visits: 5 });The user object stays clean and doesn't know about the secret data!
Real-World Analogy
The Library Card Catalog
Imagine a library with two systems:
System 1: Writing in Books ❌
Book: "Harry Potter"
Someone writes inside the cover:
"Borrowed by: Alice"
"Return date: Jan 15"
"Condition: Good"Problems:
- ❌ The book itself gets messy
- ❌ Everyone can see the notes
- ❌ Hard to remove the writing later
System 2: Separate Card Catalog ✅
Book: "Harry Potter" (stays pristine)
Card Catalog (WeakMap):
┌──────────────────────────────────┐
│ Book: Harry Potter │
│ ├─ Borrowed by: Alice │
│ ├─ Return date: Jan 15 │
│ └─ Condition: Good │
└──────────────────────────────────┘Benefits:
- ✅ The book stays clean
- ✅ Only librarians see the catalog
- ✅ Easy to update or remove entries
- ✅ When book is destroyed, the card gets automatically thrown away
That's exactly what a WeakMap does!
Regular Objects vs Maps vs WeakMaps
Option 1: Regular Object (Old School)
const userDatabase = {};
const alice = { name: 'Alice' };
// Store data about alice
userDatabase[alice] = { visits: 5 };
// Uh oh! This converts alice to a string: "[object Object]"
console.log(userDatabase);
// { "[object Object]": { visits: 5 } }Problem: Objects can only have string keys, so your object gets converted to a string! ❌
Option 2: Map (Better)
const userDatabase = new Map();
const alice = { name: 'Alice' };
const bob = { name: 'Bob' };
// Store data using objects as keys
userDatabase.set(alice, { visits: 5 });
userDatabase.set(bob, { visits: 3 });
console.log(userDatabase.get(alice)); // { visits: 5 }
console.log(userDatabase.get(bob)); // { visits: 3 }Better! Maps can use objects as keys. ✅
But there's a problem:
// Alice leaves the app
alice = null;
// But the Map still holds a reference to the old alice object
console.log(userDatabase.size); // Still 2!
// The data about alice is stuck in memory forever! 😱Option 3: WeakMap (Perfect for Metadata)
const userDatabase = new WeakMap();
let alice = { name: 'Alice' };
let bob = { name: 'Bob' };
// Store data using objects as keys
userDatabase.set(alice, { visits: 5 });
userDatabase.set(bob, { visits: 3 });
console.log(userDatabase.get(alice)); // { visits: 5 }
// Alice leaves the app
alice = null;
// The WeakMap automatically removes alice's data! 🎉
// (You can't see this directly, but it happens automatically)Perfect! When the object is no longer needed, the WeakMap entry disappears automatically. ✅
How WeakMap Works
The Basic API
WeakMap has only 4 methods:
const secrets = new WeakMap();
const obj = { name: 'Alice' };
// 1. SET - Store data
secrets.set(obj, { tracked: true, count: 0 });
// 2. GET - Retrieve data
const data = secrets.get(obj);
console.log(data); // { tracked: true, count: 0 }
// 3. HAS - Check if data exists
console.log(secrets.has(obj)); // true
// 4. DELETE - Remove data
secrets.delete(obj);
console.log(secrets.has(obj)); // falseThat's it! Super simple. 🎯
What You CAN'T Do
const secrets = new WeakMap();
// ❌ Can't loop through it
for (let item of secrets) { } // Error!
// ❌ Can't get all keys
secrets.keys(); // Doesn't exist!
// ❌ Can't get the size
secrets.size; // undefined!
// ❌ Can't clear everything
secrets.clear(); // Doesn't exist!
// ❌ Can't use primitive values as keys
secrets.set('alice', {}); // Error! Key must be an object
secrets.set(42, {}); // Error! Key must be an objectWhy these limitations? Because WeakMap is designed to be invisible and automatic. You can't inspect it because it's meant to be private metadata storage.
Step-by-Step: Building Your First WeakMap
Example: Secret User Tracking
Let's build a system that tracks user activity without modifying user objects.
Step 1: Create the WeakMap
const userActivity = new WeakMap();Think of this as creating an invisible filing cabinet.
Step 2: Create User Objects
const alice = { name: 'Alice', age: 30 };
const bob = { name: 'Bob', age: 25 };Just regular objects. Nothing special yet.
Step 3: Store Hidden Data
// Attach invisible data to alice
userActivity.set(alice, {
visits: 0,
lastLogin: null,
preferences: { theme: 'dark' }
});
// Attach invisible data to bob
userActivity.set(bob, {
visits: 0,
lastLogin: null,
preferences: { theme: 'light' }
});What just happened?
alice (the object) userActivity (the WeakMap)
┌──────────────────┐ ┌──────────────────────────┐
│ name: 'Alice' │ ←── │ alice → { │
│ age: 30 │ │ visits: 0, │
└──────────────────┘ │ lastLogin: null, │
│ preferences: {...} │
│ } │
└──────────────────────────┘The arrow is invisible! Alice doesn't know she's being tracked.
Step 4: Update the Hidden Data
// User logs in
function login(user) {
const activity = userActivity.get(user);
activity.visits++;
activity.lastLogin = new Date();
}
login(alice);
login(alice);
login(bob);
console.log(userActivity.get(alice));
// { visits: 2, lastLogin: [Date], preferences: {...} }
console.log(userActivity.get(bob));
// { visits: 1, lastLogin: [Date], preferences: {...} }Notice: We never modified alice or bob objects themselves! ✨
Step 5: The Magic Cleanup
// Alice deletes her account
alice = null;
// The WeakMap automatically removes alice's tracking data!
// (This happens behind the scenes by JavaScript's garbage collector)
// Bob's data is still there
console.log(userActivity.get(bob)); // Still works!Visual:
Before (alice exists):
┌────────────────────────────┐
│ userActivity (WeakMap) │
│ ├─ alice → { visits: 2 } │
│ └─ bob → { visits: 1 } │
└────────────────────────────┘
After (alice = null):
┌────────────────────────────┐
│ userActivity (WeakMap) │
│ └─ bob → { visits: 1 } │ ← alice's entry automatically removed!
└────────────────────────────┘The "Weak" Part Explained
What Does "Weak" Mean?
The "weak" in WeakMap means the reference is weak, not strong.
Strong Reference (Normal)
const users = [];
let alice = { name: 'Alice' };
users.push(alice); // Strong reference
// Even if you do this:
alice = null;
// The object still exists in memory because users array holds it
console.log(users[0]); // { name: 'Alice' } - Still there!Strong reference = Keeps object alive 💪
Weak Reference (WeakMap)
const metadata = new WeakMap();
let alice = { name: 'Alice' };
metadata.set(alice, { tracked: true });
// When you do this:
alice = null;
// The WeakMap does NOT keep the object alive
// JavaScript's garbage collector can clean it up
// And the WeakMap entry disappears automaticallyWeak reference = Doesn't keep object alive 🍃
Visual Comparison
Strong Reference (Array/Map)
Code says: alice = null
↓
┌────────────────────────────────────┐
│ I want to delete alice │
└────────────┬───────────────────────┘
│
↓
┌────────────────────────────────────┐
│ Sorry! The array still needs it │
│ Alice object stays in memory │
└────────────────────────────────────┘Weak Reference (WeakMap)
Code says: alice = null
↓
┌────────────────────────────────────┐
│ I want to delete alice │
└────────────┬───────────────────────┘
│
↓
┌────────────────────────────────────┐
│ No problem! WeakMap lets go │
│ Alice object gets cleaned up │
│ WeakMap entry disappears too │
└────────────────────────────────────┘Why This Is Perfect for Reactivity
The Problem: Storing Metadata
In reactive systems, we need to store extra data about objects:
// For each reactive state, we need to track:
// - Which effects depend on which properties
// - Which properties are computed
// - Cleanup functions
// Etc.Bad Solution: Modify the Object ❌
function state(data) {
const proxy = new Proxy(data, { /* ... */ });
// ❌ This pollutes the object
proxy.__deps = new Map();
proxy.__computedMap = new Map();
proxy.__cleanups = [];
return proxy;
}
const myState = state({ count: 0 });
console.log(Object.keys(myState));
// ['count', '__deps', '__computedMap', '__cleanups'] 😱Problems:
- ❌ Pollutes the object with internal properties
- ❌ Users can see and modify internal data
- ❌ Could conflict with user's property names
- ❌ Shows up in loops and Object.keys()
Good Solution: WeakMap ✅
const reactiveMap = new WeakMap(); // Invisible storage!
function state(data) {
const proxy = new Proxy(data, { /* ... */ });
// ✅ Store metadata invisibly
reactiveMap.set(proxy, {
deps: new Map(),
computedMap: new Map(),
cleanups: []
});
return proxy;
}
const myState = state({ count: 0 });
console.log(Object.keys(myState));
// ['count'] - Clean! ✨Benefits:
- ✅ Object stays pristine
- ✅ Metadata is hidden
- ✅ No naming conflicts
- ✅ Automatic cleanup when proxy is destroyed
How It's Used in DOM Helpers Reactive
const reactiveMap = new WeakMap();
function createReactive(target) {
const deps = new Map();
const computedMap = new Map();
const proxy = new Proxy(target, {
get(obj, key) {
// Access metadata
const meta = reactiveMap.get(proxy);
// Track dependency
if (currentEffect) {
if (!meta.deps.has(key)) {
meta.deps.set(key, new Set());
}
meta.deps.get(key).add(currentEffect);
}
return obj[key];
},
set(obj, key, value) {
obj[key] = value;
// Access metadata
const meta = reactiveMap.get(proxy);
// Trigger effects
const effects = meta.deps.get(key);
if (effects) {
effects.forEach(effect => effect());
}
return true;
}
});
// Store metadata invisibly
reactiveMap.set(proxy, { deps, computedMap });
return proxy;
}The flow:
1. Create Proxy
↓
2. Store metadata in WeakMap
↓
3. When proxy is accessed, look up metadata
↓
4. Use metadata to track dependencies
↓
5. When proxy is destroyed, metadata auto-cleans upCommon Questions
Q: "Why not just use a regular Map?"
Answer: Memory leaks!
// ❌ Regular Map
const metadata = new Map();
function createState() {
const state = { count: 0 };
metadata.set(state, { deps: [] });
return state;
}
// Create 1000 states
for (let i = 0; i < 1000; i++) {
createState();
}
// All 1000 states are still in memory! 😱
console.log(metadata.size); // 1000
// Even though you're not using them anymore!// ✅ WeakMap
const metadata = new WeakMap();
function createState() {
const state = { count: 0 };
metadata.set(state, { deps: [] });
return state;
}
// Create 1000 states
for (let i = 0; i < 1000; i++) {
createState();
}
// States that aren't used get cleaned up automatically! 🎉
// No memory leak!Q: "Can I see what's inside a WeakMap?"
No! That's the whole point - it's invisible.
const secrets = new WeakMap();
const obj = {};
secrets.set(obj, { password: '12345' });
// ❌ Can't inspect it
console.log(secrets); // WeakMap {} - Doesn't show contents
// ✅ Can only get specific entries
console.log(secrets.get(obj)); // { password: '12345' }Think of it like a safe deposit box - you can only access it if you have the key (the object).
Q: "Why can't I use strings or numbers as keys?"
Answer: Because WeakMap needs to know when to clean up, and primitives never get garbage collected.
// ❌ This wouldn't make sense
const map = new WeakMap();
map.set('alice', { data: 'xyz' });
// When would this entry be removed?
// Strings never get garbage collected!
// The entry would exist forever = not "weak"!Only objects can be garbage collected, so only objects can be WeakMap keys.
Q: "When should I use WeakMap vs Map?"
Use WeakMap when:
- ✅ Storing metadata about objects
- ✅ You want automatic cleanup
- ✅ You don't need to loop through entries
- ✅ The data "belongs" to the object
Use regular Map when:
- ✅ You need to iterate through entries
- ✅ You need to get the size
- ✅ You need to clear all entries
- ✅ Keys can be primitives (strings, numbers)
Practice Examples
Example 1: Object Validator
Store validation rules for objects without modifying them.
const validators = new WeakMap();
function addValidation(obj, rules) {
validators.set(obj, rules);
}
function validate(obj) {
const rules = validators.get(obj);
if (!rules) return true; // No rules = valid
for (let [field, rule] of Object.entries(rules)) {
if (!rule(obj[field])) {
return false;
}
}
return true;
}
// Create a user
const user = { name: 'Alice', age: 30 };
// Add validation rules (hidden!)
addValidation(user, {
name: (val) => val.length > 0,
age: (val) => val >= 18
});
// Validate
console.log(validate(user)); // true
user.age = 10;
console.log(validate(user)); // false
// The user object is still clean!
console.log(user); // { name: 'Alice', age: 10 }Example 2: DOM Element Cache
Store data about DOM elements.
const elementData = new WeakMap();
function trackElement(element, data) {
elementData.set(element, data);
}
function getElementData(element) {
return elementData.get(element);
}
// Track a button
const button = document.getElementById('myButton');
trackElement(button, {
clicks: 0,
created: new Date()
});
button.addEventListener('click', () => {
const data = getElementData(button);
data.clicks++;
console.log(`Clicked ${data.clicks} times`);
});
// If the button is removed from DOM and garbage collected,
// the tracking data automatically disappears!Example 3: Private Data Pattern
Create truly private properties for objects.
const privateData = new WeakMap();
class Person {
constructor(name, ssn) {
// Public property
this.name = name;
// Private property (stored in WeakMap)
privateData.set(this, { ssn });
}
getSSN() {
// Only this class can access it
return privateData.get(this).ssn;
}
}
const alice = new Person('Alice', '123-45-6789');
console.log(alice.name); // 'Alice' - public
console.log(alice.ssn); // undefined - can't access!
console.log(alice.getSSN()); // '123-45-6789' - through method only
// Can't see private data from outside
console.log(Object.keys(alice)); // ['name'] - Clean!Example 4: Build a Simple Dependency Tracker
Track which functions depend on which objects.
const dependencies = new WeakMap();
function track(obj, callback) {
if (!dependencies.has(obj)) {
dependencies.set(obj, new Set());
}
dependencies.get(obj).add(callback);
}
function notify(obj) {
const callbacks = dependencies.get(obj);
if (callbacks) {
callbacks.forEach(fn => fn());
}
}
// Create an object
const state = { count: 0 };
// Track dependencies
track(state, () => console.log('Callback 1 ran!'));
track(state, () => console.log('Callback 2 ran!'));
// Trigger callbacks
notify(state);
// Callback 1 ran!
// Callback 2 ran!
// The state object is still clean!
console.log(state); // { count: 0 }Summary
What is WeakMap?
An invisible storage system that lets you attach hidden metadata to objects without modifying them.
Object (clean) ←→ WeakMap (hidden metadata)The 4 Methods
const map = new WeakMap();
map.set(obj, data); // Store
map.get(obj); // Retrieve
map.has(obj); // Check
map.delete(obj); // RemoveThe "Weak" Part
WeakMap doesn't prevent garbage collection.
Regular Map: Keeps objects alive (strong reference)
WeakMap: Lets objects be cleaned up (weak reference)When to Use WeakMap
✅ Use WeakMap when:
- Storing private/metadata about objects
- Want automatic memory cleanup
- Don't need to iterate or get size
- Keys are objects
❌ Don't use WeakMap when:
- Need to loop through entries
- Need to check size or clear all
- Keys are primitives (strings, numbers)
Why It's Perfect for Reactive Systems
// ✅ WeakMap keeps reactive metadata invisible
const reactiveMap = new WeakMap();
function state(data) {
const proxy = new Proxy(data, { /* ... */ });
// Store metadata without polluting the object
reactiveMap.set(proxy, {
deps: new Map(),
computed: new Map()
});
return proxy;
}Benefits:
- ✅ Objects stay clean
- ✅ No property name conflicts
- ✅ Automatic cleanup
- ✅ Hidden from users
WeakMap is the secret ingredient that lets reactive systems store data invisibly! ✨