JavaScript Proxy and Reflect Metaprogramming
Understand Proxy traps, Reflect forwarding, prototype and extensibility operations, proxy invariants, revocation, and practical metaprogramming boundaries.
Intermediate
Proxy and Reflect expose a controlled metaprogramming layer around JavaScript object operations. A Proxy can intercept operations such as property reads, writes, existence checks, own-key enumeration, calls, and construction. Reflect provides functions that perform the corresponding standard operations explicitly, making it useful for forwarding behavior from a trap.
A Proxy wraps a target with a handler
new Proxy(target, handler) creates a Proxy exotic object. The target is the object whose behavior is being wrapped; the handler contains optional trap methods. If a trap is missing, the corresponding operation is forwarded to the target. The Proxy itself is the object your code interacts with.
Output
Ready to run.
Quick check
What does a Proxy handler contain?
const result = ;
The get trap intercepts property reads
The get trap corresponds to the target object’s property-get operation. Its usual parameters are the target, the property key, and the receiver. Returning Reflect.get(target, property, receiver) preserves ordinary lookup semantics while still allowing instrumentation or policy around the operation.
Output
Ready to run.
Quick check
Which Reflect operation naturally forwards a get trap?
const result = ;
The set trap intercepts property writes
The set trap receives target, property, value, and receiver. A trap should normally return a Boolean-compatible result indicating whether the write succeeded. Reflect.set is useful when the trap wants to add behavior but preserve the normal [[Set]] semantics.
Output
Ready to run.
Quick check
What should a set trap normally return?
const result = ;
The has trap controls the in operator
The has trap intercepts property-existence checks such as property in object. It can implement policy or instrumentation, but proxy invariants restrict what it may report for certain non-configurable target properties.
Output
Ready to run.
Quick check
Which operator invokes the proxy has trap?
const result = ;
The deleteProperty trap intercepts delete
deleteProperty can observe or control deletion attempts. Reflect.deleteProperty(target, property) is the direct forwarding operation. As with other traps, the target’s descriptor state places limits on what a proxy may claim.
Output
Ready to run.
Quick check
Which Reflect method matches deleteProperty?
const result = ;
ownKeys controls own-key enumeration
The ownKeys trap intercepts operations that request an object’s own property keys, such as Reflect.ownKeys(proxy) and operations built on it. The trap must obey ordering and invariant requirements imposed by the Proxy internal method; it cannot simply invent an arbitrary impossible view of a non-extensible target.
Output
Ready to run.
Quick check
Which operation directly asks for all own keys including symbols?
const result = ;
defineProperty and getOwnPropertyDescriptor traps affect descriptors
defineProperty intercepts attempts to define or redefine an own property. getOwnPropertyDescriptor intercepts descriptor retrieval. These traps are tied to the same internal operations taught in the property-descriptor lesson, so their results are constrained by target descriptors and proxy invariants.
Output
Ready to run.
Quick check
Which Reflect function forwards a property definition?
const result = ;
Reflect is a forwarding vocabulary, not a second object model
Reflect methods expose many internal operations as ordinary functions. They are especially useful in traps because they accept the same target, key, value, receiver, or descriptor information that the corresponding operation needs. Reflect does not remove Proxy invariants; it gives you a precise way to delegate to standard behavior.
Output
Ready to run.
Quick check
Why is Reflect.set useful inside a set trap?
const result = ;
Proxy invariants are hard constraints
Proxy traps do not grant unlimited freedom. ECMAScript requires proxy results to remain consistent with essential internal-method invariants. For example, a trap cannot report a different value for a non-writable, non-configurable own data property, and an ownKeys result cannot omit required non-configurable keys. Violating an enforced invariant produces a TypeError.
Output
Ready to run.
Quick check
What can happen when a proxy trap violates an enforced invariant?
const result = ;
A missing trap delegates to the target
Handlers are sparse by design. If a handler does not define a trap for an operation, the proxy forwards that operation to the target. This lets a handler intercept only the behavior it needs to observe or change.
Output
Ready to run.
Quick check
What happens when the handler has no trap for an operation?
const result = ;
Revocable proxies can be shut down
Proxy.revocable(target, handler) returns an object containing a proxy and a revoke function. Calling revoke invalidates that proxy. Subsequent internal-method operations on the revoked proxy throw TypeError because its target and handler are no longer available for use.
Output
Ready to run.
Quick check
What does revoke() do to a revocable proxy?
const result = ;
Function proxies can intercept calls and construction
If the proxy target is callable, the proxy has a call internal method and can use an apply trap. If the target is constructable, the proxy can also use a construct trap. The construct trap must return an object. This connects Proxy to the function-object model from the previous lessons.
Output
Ready to run.
Quick check
Which trap intercepts a call to a callable proxy?
const result = ;
The prototype traps intercept prototype operations
A Proxy can intercept prototype reads and writes through getPrototypeOf and setPrototypeOf. Reflect.getPrototypeOf and Reflect.setPrototypeOf perform the corresponding ordinary operations explicitly. These traps are subject to invariants: for example, a non-extensible target cannot report a different prototype.
Output
Ready to run.
Quick check
Which trap intercepts Object.getPrototypeOf(proxy)?
const result = ;
Output
Ready to run.
Quick check
Which trap intercepts Object.setPrototypeOf(proxy, value)?
const result = ;
Extensibility traps must agree with the target
isExtensible and preventExtensions intercept extensibility checks and attempts to make a target non-extensible. Their trap results are constrained by the target: a proxy cannot simply report an extensibility state that disagrees with its target.
Output
Ready to run.
Quick check
Which trap intercepts Object.isExtensible(proxy)?
const result = ;
Output
Ready to run.
Quick check
Which trap intercepts Object.preventExtensions(proxy)?
const result = ;
Use Proxy for behavior boundaries, not mystery magic
Proxy is powerful but changes fundamental object operations. Good uses include validation, logging, access policies, virtualization, compatibility layers, and controlled API boundaries. Keep handlers small, document intercepted operations, and use Reflect to preserve ordinary semantics when that is the goal. Avoid proxies when a plain function, class, or explicit wrapper communicates the design more clearly.
Authoring rules
Think in terms of the operation being intercepted: get, set, has, deleteProperty, ownKeys, getOwnPropertyDescriptor, defineProperty, apply, or construct. Start from ordinary target behavior, then add the smallest necessary policy. Prefer Reflect forwarding when you want normal semantics. Treat invariants as requirements, not obstacles to work around. Remember that a proxy does not recursively wrap everything reachable from its target; only operations performed through that proxy are intercepted.
Practice
Build a validation proxy around a configuration object. Reject unknown writes, log reads, and forward accepted operations with Reflect. Then add a get trap that exposes a computed virtual property without adding it to the target. Inspect the target and proxy separately to see the difference between virtual behavior and stored state. Finally create a revocable proxy and demonstrate its lifecycle.
Specification references
The current ECMA-262 specification defines Proxy exotic objects, their [[ProxyHandler]] and [[ProxyTarget]] internal slots, proxy traps corresponding to essential internal methods, proxy invariants, Proxy.revocable(), and the Reflect operations used to invoke object behavior explicitly. urlECMA-262 — current language specificationhttps://tc39.es/ecma262/
Next step
The next JavaScript phase moves into collections and iteration: iterable protocols, iterators, for...of, generator functions, Map, Set, WeakMap, and WeakSet.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.