Proxy (The Interceptor)
The Magic Trick Revealed
Remember when we said this was "magic"?
const state = state({ count: 0 });
effect(() => {
console.log(state.count);
});
state.count = 5; // Effect automatically runs! ✨How does the computer know to run the effect when state.count changes?
The secret is... Proxy! 🎩✨
What is a Proxy? (No Jargon)
Simple Definition
A Proxy is like a middleman that sits between you and an object.
Instead of talking to the object directly, you talk to the Proxy, and the Proxy talks to the object for you.
But here's the magic: The Proxy can do extra stuff before passing your message along.
Super Simple Analogy
Think of a Proxy like a secretary at a company:
You → Secretary → Boss- You want to talk to the Boss (the object)
- But you go through the Secretary (the Proxy) first
- The Secretary can:
- ✅ Log your visit: "Someone asked for the boss at 2pm"
- ✅ Check if you're allowed to talk to the boss
- ✅ Notify others: "Hey everyone, someone's talking to the boss!"
- ✅ Then pass your message to the boss
The Proxy is that secretary!
Real-World Analogy
The Hotel Receptionist
Imagine you're staying at a hotel:
┌─────────────────────────────────────┐
│ You want to access your room │
└───────────────┬─────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ Receptionist (Proxy) │
│ - Checks your keycard │
│ - Logs your entry time │
│ - Notifies cleaning staff │
│ - THEN unlocks your door │
└───────────────┬─────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ Your Room (The Object) │
└─────────────────────────────────────┘The receptionist doesn't change your room, but they know every time you access it and can do things before you get in.
That's exactly what a Proxy does with objects!
How Regular Objects Work
Without Proxy (Direct Access)
// Regular object
const user = {
name: 'Alice',
age: 30
};
// You access it directly
console.log(user.name); // Just gets the value
user.age = 31; // Just sets the value
// The computer has NO IDEA you did this!
// No logging, no notifications, nothing! 🤷Flow:
You
↓
user.name
↓
Get value directlyProblem: You can't detect when someone reads or changes the object.
How Proxy Objects Work
With Proxy (Intercepted Access)
// Create a Proxy
const userProxy = new Proxy(user, {
get(target, property) {
console.log(`Someone is reading ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Someone is changing ${property} to ${value}`);
target[property] = value;
return true;
}
});
// Now use the proxy
console.log(userProxy.name);
// Logs: "Someone is reading name"
// Returns: "Alice"
userProxy.age = 31;
// Logs: "Someone is changing age to 31"
// Sets age to 31Flow:
You
↓
userProxy.name
↓
Proxy intercepts (runs your code)
↓
Get value from original object
↓
Return value to youMagic: You can now detect and react to every access! 🎉
Step-by-Step: Building Your First Proxy
Step 1: Start with a Regular Object
const person = {
name: 'Bob',
age: 25
};
console.log(person.name); // Bob
person.age = 26;
console.log(person.age); // 26Nothing special here. Just a normal object.
Step 2: Create a Proxy Wrapper
const personProxy = new Proxy(person, {
// This object contains "traps"
// Traps are functions that intercept operations
});Think of it like this:
personProxy = Wrapper around 'person'Step 3: Add a "get" Trap (Intercept Reads)
const personProxy = new Proxy(person, {
get(target, property) {
// target = the original object (person)
// property = the property name being accessed
console.log(`📖 Reading: ${property}`);
// Return the actual value
return target[property];
}
});
// Try it!
console.log(personProxy.name);
// Logs: "📖 Reading: name"
// Returns: "Bob"What's happening:
- You ask for
personProxy.name - JavaScript calls your
getfunction - Your function logs a message
- Your function returns
target[property](which isperson.name) - You get
"Bob"back
Step 4: Add a "set" Trap (Intercept Writes)
const personProxy = new Proxy(person, {
get(target, property) {
console.log(`📖 Reading: ${property}`);
return target[property];
},
set(target, property, value) {
// target = the original object (person)
// property = the property name being set
// value = the new value
console.log(`✏️ Writing: ${property} = ${value}`);
// Actually set the value
target[property] = value;
// Return true to indicate success
return true;
}
});
// Try it!
personProxy.age = 26;
// Logs: "✏️ Writing: age = 26"
console.log(personProxy.age);
// Logs: "📖 Reading: age"
// Returns: 26Step 5: Do Something Cool (Track Changes)
const changes = [];
const personProxy = new Proxy(person, {
get(target, property) {
return target[property];
},
set(target, property, value) {
// 🎯 Track every change!
changes.push({
property: property,
oldValue: target[property],
newValue: value,
timestamp: new Date()
});
target[property] = value;
return true;
}
});
// Make some changes
personProxy.age = 26;
personProxy.name = 'Bobby';
personProxy.age = 27;
// Check the log
console.log(changes);
/*
[
{ property: 'age', oldValue: 25, newValue: 26, timestamp: ... },
{ property: 'name', oldValue: 'Bob', newValue: 'Bobby', timestamp: ... },
{ property: 'age', oldValue: 26, newValue: 27, timestamp: ... }
]
*/You just built a change tracker! 🎉
The Two Main "Traps"
Think of "traps" as interception points. Like speed traps on a highway that catch you doing something.
1. The get Trap (Reading)
When it runs: Every time you read a property.
get(target, property) {
// target = the original object
// property = which property is being read
// Do your custom stuff here
console.log(`Reading ${property}`);
// Return the value
return target[property];
}Examples of when it runs:
const value = obj.name; // Triggers get trap
console.log(obj.age); // Triggers get trap
const result = obj.city; // Triggers get trap
if (obj.active) { } // Triggers get trap2. The set Trap (Writing)
When it runs: Every time you write a property.
set(target, property, value) {
// target = the original object
// property = which property is being set
// value = the new value
// Do your custom stuff here
console.log(`Setting ${property} to ${value}`);
// Actually set the value
target[property] = value;
// Must return true
return true;
}Examples of when it runs:
obj.name = 'Alice'; // Triggers set trap
obj.age = 30; // Triggers set trap
obj.city = 'Paris'; // Triggers set trap
obj.active = true; // Triggers set trapWhy This Is Magic for Reactivity
The Problem We're Solving
Remember this?
// We want this to work automatically
const state = state({ count: 0 });
effect(() => {
document.getElementById('count').textContent = state.count;
});
state.count = 5; // How does the effect know to re-run? 🤔The Solution: Proxy!
Here's how DOM Helpers Reactive uses Proxy:
function state(data) {
// Create a Proxy wrapper
const proxy = new Proxy(data, {
get(target, property) {
// 🎯 TRACK: "Someone is reading this property"
if (currentEffect) {
// Remember: "currentEffect depends on this property"
trackDependency(property, currentEffect);
}
return target[property];
},
set(target, property, value) {
// Set the new value
target[property] = value;
// 🎯 TRIGGER: "This property changed, run dependent effects"
triggerEffects(property);
return true;
}
});
return proxy;
}Step-by-Step Flow
1. Create reactive state:
const state = state({ count: 0 });
// Returns a Proxy wrapper around { count: 0 }2. Create effect:
let currentEffect = null;
effect(() => {
console.log(state.count);
});
// Under the hood:
currentEffect = effectFunction;
effectFunction(); // Runs the effect3. Effect reads state.count:
// When state.count is accessed:
// → Proxy's get trap fires
// → Sees currentEffect is set
// → Records: "effectFunction depends on 'count'"4. Change state.count:
state.count = 5;
// Proxy's set trap fires:
// 1. Sets count to 5
// 2. Looks up effects that depend on 'count'
// 3. Finds effectFunction
// 4. Runs effectFunction
// 5. Console logs: 5Visual Flow
┌─────────────────────────────────────┐
│ state.count = 5 │
└────────────┬────────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ Proxy's SET trap intercepts │
│ "Someone is setting count to 5" │
└────────────┬────────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ Look up: Who depends on 'count'? │
│ → effectFunction │
└────────────┬────────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ Run effectFunction │
│ → Updates DOM automatically ✨ │
└─────────────────────────────────────┘Common Questions
Q: "Is the Proxy a copy of the object?"
No! The Proxy is a wrapper around the original object.
const original = { count: 0 };
const proxy = new Proxy(original, {});
proxy.count = 5;
console.log(original.count); // 5 (same object!)Think of it like this:
Original Object: The actual book
Proxy: A protective cover around the bookWhen you write in the book through the cover, you're still writing in the same book.
Q: "Do I always use the Proxy, never the original?"
Yes! Always use the Proxy if you want the interception to work.
const original = { count: 0 };
const proxy = new Proxy(original, {
set(target, prop, value) {
console.log('Changed!');
target[prop] = value;
return true;
}
});
// ✅ Use the proxy
proxy.count = 5; // Logs: "Changed!"
// ❌ Use the original directly
original.count = 10; // No log! Proxy doesn't know!Q: "Can I have multiple Proxies for one object?"
Yes! Each Proxy can have different behavior.
const user = { name: 'Alice' };
const logger = new Proxy(user, {
set(target, prop, value) {
console.log(`Logging: ${prop} = ${value}`);
target[prop] = value;
return true;
}
});
const validator = new Proxy(user, {
set(target, prop, value) {
if (typeof value !== 'string') {
throw new Error('Name must be a string!');
}
target[prop] = value;
return true;
}
});
logger.name = 'Bob'; // Logs change
validator.name = 'Charlie'; // Validates typeQ: "What if I want the original object back?"
Use a Symbol!
const RAW = Symbol('raw');
const proxy = new Proxy(original, {
get(target, prop) {
if (prop === RAW) {
return target; // Return original!
}
return target[prop];
}
});
// Get the original
const orig = proxy[RAW];This is exactly what DOM Helpers does!
Practice Examples
Example 1: Build a Read Counter
Goal: Count how many times properties are read.
const readCounts = {};
const data = { name: 'Alice', age: 30 };
const counter = new Proxy(data, {
get(target, property) {
// Count the read
if (!readCounts[property]) {
readCounts[property] = 0;
}
readCounts[property]++;
return target[property];
}
});
// Use it
console.log(counter.name); // Alice
console.log(counter.name); // Alice
console.log(counter.age); // 30
console.log(readCounts);
// { name: 2, age: 1 }Example 2: Build a Validator
Goal: Prevent invalid values.
const user = { age: 25 };
const validated = new Proxy(user, {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number') {
throw new Error('Age must be a number!');
}
if (value < 0 || value > 150) {
throw new Error('Invalid age!');
}
}
target[property] = value;
return true;
}
});
validated.age = 30; // ✅ Works
validated.age = -5; // ❌ Error: Invalid age!
validated.age = 'thirty'; // ❌ Error: Age must be a number!Example 3: Build a Simple Reactive System
Goal: Automatically run functions when data changes.
let currentWatcher = null;
const watchers = {};
function watch(fn) {
currentWatcher = fn;
fn(); // Run once to track dependencies
currentWatcher = null;
}
function reactive(obj) {
return new Proxy(obj, {
get(target, property) {
// Track dependency
if (currentWatcher) {
if (!watchers[property]) {
watchers[property] = [];
}
watchers[property].push(currentWatcher);
}
return target[property];
},
set(target, property, value) {
target[property] = value;
// Trigger watchers
if (watchers[property]) {
watchers[property].forEach(fn => fn());
}
return true;
}
});
}
// Use it!
const state = reactive({ count: 0 });
watch(() => {
console.log('Count is:', state.count);
});
// Logs: "Count is: 0"
state.count = 5;
// Logs: "Count is: 5"
state.count = 10;
// Logs: "Count is: 10"You just built a mini reactivity system! 🎉
Summary
What is a Proxy?
A middleman that intercepts operations on objects.
You → Proxy (intercepts) → ObjectThe Two Main Traps
gettrap - Runs when reading propertiessettrap - Runs when writing properties
Basic Syntax
const proxy = new Proxy(originalObject, {
get(target, property) {
// Intercept reads
return target[property];
},
set(target, property, value) {
// Intercept writes
target[property] = value;
return true;
}
});Why This Matters for Reactivity
Proxy lets us:
- ✅ Detect when data is read (track dependencies)
- ✅ Detect when data changes (trigger updates)
- ✅ Build automatic, reactive systems
Without Proxy:
let count = 0;
// No way to know when count is read or changed 😢With Proxy:
const state = new Proxy({ count: 0 }, {
get(t, k) { console.log('Read!'); return t[k]; },
set(t, k, v) { console.log('Changed!'); t[k] = v; return true; }
});
// We know everything! 🎉The Proxy is the foundation that makes all the reactivity magic possible! ✨