JavaScript Proxy Table

TrapFires whenField note
get(target, prop, receiver)Any property READThe validation/logging hook - receiver is the proxy or a prototype-derived object
set(target, prop, value, receiver)Any property WRITEReturn true to confirm; a falsy return fails the write (silent in sloppy code, TypeError in strict)
has(target, prop)The in operatorHide internals: return false for _private keys - for...in is NOT trapped (ownKeys covers it)
deleteProperty(target, prop)The delete operatorImmutable layers: refuse deletes on frozen records and audit logs
apply(target, thisArg, args)Any function CALLWrap timing, memoization, access control - the AOP workhorse
construct(target, args)The new operatorMust return an object - returning a primitive throws TypeError
ownKeys(target)Object.keys / getOwnPropertyNamesCannot omit non-configurable own keys - the invariant guard
revocable(target, handler)Factory, not a trapReturns {proxy, revoke} - revoke() makes every later operation throw; the sandbox escape hatch
Reference: the MDN Proxy reference. A Proxy intercepts the FUNDAMENTAL operations of an object (read, write, has, delete, call, construct) rather than its individual properties - one handler layer stands in front of the whole object. The engine enforces invariants: traps cannot lie about the target's real state (report a non-configurable property as missing and you get a TypeError), so meta-programming ships with guardrails. Bottom line: set-trap validation gives you runtime schema checks without touching the target, apply-trap wrapping is the cleanest logging and memoization layer, and revocable() is capability revocation for sandboxes - Vue 3's reactivity system is exactly this table in production. Related tools: JS iterator table (traps on Symbol.iterator enable lazy proxies), JS error table (the invariant TypeErrors), and strict mode table (why falsy set returns throw there).

A Proxy wraps an object and intercepts its FUNDAMENTAL operations - every read, write, in check, delete, function call and new expression passes through your handler's trap first. One handler layer stands in front of the whole object: you write a get trap once and every property read is validated, logged or defaulted.

Bottom line: the set trap is a runtime schema validator that never touches the target, the apply trap is the cleanest logging and memoization wrapper there is, and revocable() hands you capability revocation - the raw material of sandboxes. Vue 3's entire reactivity system is this table running in production.

The honest part: the engine enforces invariants on what traps may claim. Report a non-configurable property as missing, return a primitive from construct, and you get a TypeError instead of a silently broken object - meta-programming with guardrails, not a blank cheque.

How to use

  1. Create one: new Proxy(target, handler) - the handler object's methods ARE the traps; an empty handler behaves exactly like the target.
  2. Validate writes in set: check value against your schema, return true - a falsy return fails the assignment (throws TypeError in strict mode).
  3. Wrap functions with apply: measure timing, cache results or gate access - the target function never knows it was wrapped.

Frequently asked questions

What is the difference between a Proxy and the target object?

The target is the real object holding the data; the Proxy is the front door every operation walks through. The proxy holds a reference to the target and forwards what its traps do not intercept. Identity is preserved in one direction only: proxy === target is false, but operations the handler does not trap behave exactly as on the target. Code that stores the target directly bypasses every trap - keep the target private, hand out only the proxy, or validation can be skipped by anyone with the raw reference.

Why does my get trap break method calls?

The classic this-detach bug: a get trap that returns target.method hands back the function WITHOUT its receiver, so this is undefined (or global) when it runs. Fix it by binding or by Reflect.get(target, prop, receiver), which forwards the receiver so methods run against the proxy. This is also why frameworks combine Proxy with Reflect - Reflect's methods take the receiver argument that keeps this pointed at the proxy through every forwarded operation.

What are Proxy invariants and when do they throw?

Invariants are the engine's protection against traps that lie about the target's real state. ownKeys cannot omit non-configurable own keys; get cannot return undefined for a non-configurable, non-writable property; construct must return an object. Violate one and you get a TypeError at the operation site - the philosophy is that proxies may intercept semantics but may not falsify them, so existing code that relies on property configurability keeps working even under surveillance.

When should I use Proxy instead of getters, setters or decorators?

Use property accessors when you know the keys at author time and want per-property control - they are faster and simpler. Reach for Proxy when the keys are unknown or unbounded: validating every write to a form state bag, logging every call on an API client, revoking access to an object after a session ends. revocable() has no accessor equivalent - it is the only way to make an object permanently unusable from the outside, which is why sandboxed eval environments are built on it.

Related tools