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.

May 1, 20263 min read1 / 6

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

  1. 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.
  2. Traps are the handler's methods: Each trap corresponds to a JavaScript operation -- get (reading a property), set (writing a property), has (the in operator), deleteProperty, apply (calling a function), and more.
  3. The set trap is the reactive one: Every time a property is written on the proxied object, set fires. This is where you can broadcast that data has changed.
  4. Always return true from set: If set does not return a truthy value, the browser logs a warning and the assignment is treated as rejected.
  5. 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

JavaScript
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

JavaScript
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 fine

set 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:

TrapFires when
getA property is read
setA property is written
hasThe in operator is used
deletePropertyA property is deleted
applyThe proxied object is called as a function
constructThe proxied object is used with new
getOwnPropertyDescriptorObject.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: