← Academy
JavaScriptTypeScriptAdvanced

JS Proxies: Hands-On Guide

Martin Slaby··8 min read

A Proxy wraps an object and intercepts operations on it — reads, writes, deletes, function calls. You define what happens when those operations occur.

The basics

Two arguments: the target (the object you're wrapping) and a handler with traps — functions that run when something happens to the proxy.

basic-proxy.js — get & set traps

get fires on reads, set on writes. Code using the proxy has no idea it's not talking to the real object. That's the whole point.

Validation

You can enforce constraints at the object level. Every write goes through the set trap, so validation happens in one place regardless of who's writing.

validation-proxy.js — runtime type checking

Reactive state

This is how Vue does reactivity under the hood. Property changes go through set, which notifies anyone who subscribed. ~20 lines:

reactive-proxy.js — subscribe to state changes

Memoization

The apply trap intercepts function calls. Wrap a function in a proxy, cache results by arguments, return cached values on repeat calls. The caller doesn't know caching exists.

memoize-proxy.js — transparent function caching

Access control

Make properties read-only, hide fields from Object.keys(), block access to sensitive data. All without touching the original object.

access-control-proxy.js — read-only & hidden fields

Default values

Instead of obj.prop ?? fallback everywhere, make the object itself return defaults for missing keys.

defaults-proxy.js — automatic fallbacks

All traps

The examples above use get, set, apply, and ownKeys. Here's everything you can intercept:

TrapInterceptsTriggered by
getProperty readproxy.name
setProperty writeproxy.name = 'x'
hasin operator'name' in proxy
deletePropertyDeletiondelete proxy.name
applyFunction callproxy(args)
constructnewnew proxy()
ownKeysKey enumerationObject.keys(proxy)
getOwnPropertyDescriptorDescriptor lookupObject.getOwnPropertyDescriptor()
definePropertyProperty definitionObject.defineProperty()
getPrototypeOfPrototype readObject.getPrototypeOf()
setPrototypeOfPrototype writeObject.setPrototypeOf()
isExtensibleExtensibility checkObject.isExtensible()
preventExtensionsLock objectObject.preventExtensions()

Trade-offs

  • Performance — every trapped operation has overhead. Don't wrap objects in hot loops.
  • Debugging — stack traces include the proxy layer. DevTools shows the proxy wrapper, not the original object.
  • Equalityproxy !== target. Strict equality checks against the original break.
  • No polyfill — Proxies can't be polyfilled. Old browsers are out.

Use them at system boundaries — config, API layers, state management — where the interception cost is negligible compared to the actual work being done.