Javascript Proxy
A Proxy wraps an object and intercepts every read or write. It is how vanilla JS can react to data changes the same way frameworks do internally.
I always assumed reactivity -- the ability to automatically update the UI when data changes -- required a framework. Vue, React, MobX all have it. But the browser has had the building block for it since ES2015: the Proxy.
The Essentials
- A Proxy wraps an existing object: You create
new Proxy(target, handler). Anyone talking to the proxy talks to the original object, but the handler intercepts every operation. - Traps are the handler's methods: Each trap corresponds to a JavaScript operation --
get(reading a property),set(writing a property),has(theinoperator),deleteProperty,apply(calling a function), and more. - The
settrap is the reactive one: Every time a property is written on the proxied object,setfires. This is where you can broadcast that data has changed. - Always
return truefromset: Ifsetdoes not return a truthy value, the browser logs a warning and the assignment is treated as rejected. - Proxies only work on objects: You cannot proxy a primitive. For reactive primitives you would need a class with getters and setters.
What a Proxy Looks Like
const user = { name: 'Denver', age: 30 };
const s = new Proxy(user, {
get(target, property) {
if (property === 'age') {
return target[property] + ' years old';
}
return target[property];
}
});
console.log(s.age); // "30 years old"
console.log(s.name); // "Denver"The proxy intercepts s.age and appends the string. s.name passes straight through. The original user object is untouched.
The set Trap for Validation
const s = new Proxy(user, {
set(target, property, value) {
if (property === 'age' && typeof value !== 'number') {
throw new TypeError('age must be a number');
}
target[property] = value;
return true;
}
});
s.age = 'thirty'; // throws TypeError
s.age = 31; // works fineset receives the target object, the property name as a string, and the new value. You validate, then assign if acceptable, then return true to confirm the assignment was handled.
Available Traps
The handler object recognizes these traps:
| Trap | Fires when |
|---|---|
get | A property is read |
set | A property is written |
has | The in operator is used |
deleteProperty | A property is deleted |
apply | The proxied object is called as a function |
construct | The proxied object is used with new |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor is called |
For reactive UI purposes, set is the one that matters. It fires every time data changes.
Why Frameworks Use This Pattern
Vue 3 uses Proxy internally for its reactivity system. When you write state.count = 1, Vue's proxy intercepts that assignment and schedules a re-render. The public API is different, but the mechanism is the same trap-based interception.
MobX uses Proxy in modern environments for the same reason. What looks like a plain object assignment is actually a call through a proxy that notifies observers.
Understanding Proxy does not mean you need to build your own framework, but it does mean you understand what frameworks are actually doing underneath.
Further Reading and Watching
- MDN: Proxy - Full reference for all available traps and their signatures.
- MDN: Reflect - The companion API to Proxy, often used inside traps to forward operations to the target.
Video:
- JavaScript Proxy in 15 Minutes by Fireship. Practical walkthrough of Proxy traps with real use cases.
Keep reading