ThisCalculator

Everyday

JavaScript Proxy and Reflect: Traps, Forwarding, Pitfalls

Updated · Standard formula, shown below

JavaScript Proxy and Reflect: Traps, Forwarding, Pitfalls

A JavaScript Proxy lets an object intercept operations such as reading a property or calling a function, while Reflect provides methods for performing those operations with their ordinary language behavior. Together they support transparent forwarding and controlled customization, but a proxy can violate expectations about identity, receivers, invariants, and performance if its traps are incomplete or inconsistent.

What Proxy and Reflect Are For

A proxy is a wrapper around a target object. You create one with new Proxy(target, handler); the handler contains optional methods called traps. A trap runs when code performs a corresponding operation on the proxy. For example, a get trap can run for property access, and a set trap can run for assignment.

Reflect is a built-in object whose methods correspond to many of these fundamental operations. Reflect.get(target, key, receiver) performs a property read; Reflect.set performs a write. These methods are not proxies and do not make an object reactive by themselves. They are useful inside traps because they let the trap delegate an operation while preserving its normal semantics and receiving a success or failure result.

Common applications include validation, logging, virtual properties, access control within an application, and observing reads or writes. A proxy is not a universal replacement for getters, setters, or explicit methods: those simpler tools are often easier to understand when only a few known properties need customization.

How Traps Intercept Operations

The handler is a set of optional trap functions. If a relevant trap is absent, JavaScript generally forwards that operation to the target. A handler can therefore customize only the operations it needs. Common traps include:

  • get(target, key, receiver) for reading a property.
  • set(target, key, value, receiver) for assigning a property.
  • has(target, key) for the in operator.
  • deleteProperty(target, key) for the delete operator.
  • ownKeys(target) for listing own property keys.
  • getOwnPropertyDescriptor(target, key) for retrieving a property descriptor.
  • apply(target, thisArg, argumentsList) for calling a function target, and construct(target, argumentsList, newTarget) for constructing with a constructor target.

For example, this proxy logs reads and then forwards each one:

const target = { name: "Ada" };
const proxy = new Proxy(target, {
  get(target, key, receiver) {
    console.log("Read:", key);
    return Reflect.get(target, key, receiver);
  }
});

proxy.name; // logs "Read: name" and returns "Ada"

The receiver is usually the proxy through which the property access occurred. Passing it to Reflect.get matters when the target has getters that use this, particularly with inheritance. Similarly, forwarding writes with Reflect.set(target, key, value, receiver) preserves receiver-sensitive behavior.

Forwarding Correctly with Reflect

A forwarding trap delegates to the matching Reflect method, often after applying its own logic. Reflect methods resemble the operations that triggered the trap, but they accept the target explicitly and return useful results. For example, Reflect.set returns a boolean indicating whether the assignment succeeded, whereas an assignment expression normally evaluates to the assigned value.

A validation trap can reject bad input and forward acceptable values:

const settings = new Proxy({}, {
  set(target, key, value, receiver) {
    if (key === "volume" && (typeof value !== "number" || value < 0 || value > 10)) {
      throw new RangeError("volume must be between 0 and 10");
    }
    return Reflect.set(target, key, value, receiver);
  }
});

Returning the result of Reflect.set is significant: a set trap must return a boolean-compatible result, and its return value influences whether assignment is considered successful. In strict mode, a false result can cause an assignment to throw. A trap that simply returns true without performing the write can make callers believe the value was stored when it was not.

Reflect is not a magical way to bypass a proxy. If a trap calls a Reflect method on the proxy itself instead of the target, the operation can trigger the same trap again and recurse indefinitely. In a handler, use the provided target as the first argument unless recursion is intentional.

Invariants and Important Pitfalls

JavaScript constrains proxies so they cannot report certain impossible states. These rules are called proxy invariants and protect object semantics. For example, a proxy cannot claim that a non-configurable, non-writable own data property has a different value through its get trap. Nor can an ownKeys trap omit a non-configurable own property. Violating an invariant causes a TypeError.

Other common problems include:

  • Lost method receivers: A method retrieved through a proxy may receive the proxy as this. Some built-in objects require internal state on the actual instance, so calling their methods with a proxy receiver can fail. A generic “bind every function to the target” workaround changes method identity and behavior, so it should not be applied blindly.
  • Identity surprises: A proxy and its target are different objects. They are not equal under ===, and using one versus the other as a Map key or WeakMap key refers to different entries.
  • Incomplete observation: A proxy sees operations performed through that proxy, not changes made directly to the target or through another reference. To observe a system consistently, code must use the proxy consistently.
  • Nested objects: Wrapping an object does not automatically wrap objects stored inside it. Deep observation requires deliberately creating or retrieving proxies for nested values.
  • Misleading enumeration: Traps such as ownKeys and getOwnPropertyDescriptor interact with reflection and enumeration. A custom result must remain compatible with target properties and proxy invariants.

Proxies also add indirection and can complicate debugging. Avoid assuming that they will be as fast as direct property access; performance depends on the engine and workload, so measure a real application if it matters.

Choosing a Proxy, Reflect, or a Simpler Tool

Use a proxy when behavior must apply broadly to operations on an object—for example, logging arbitrary property reads or implementing a dynamic property interface. Use Reflect when you need to invoke a fundamental operation explicitly, including inside a trap. It is often best to combine a small amount of custom behavior with Reflect-based forwarding.

For a fixed set of properties, ordinary getters and setters are often clearer. For validation at a boundary, a named function or constructor can make the rules more explicit. For change notifications, explicit update methods may be easier to audit than intercepting every assignment. Choose a proxy when its broad interception is genuinely useful, not merely because it is available.

Frequently Asked Questions

Does Reflect create a proxy?
No. Reflect is a collection of static methods for performing object operations. Proxy creates an object that can intercept operations.

Do I need to define every trap?
No. Traps are optional. Operations without a corresponding trap are normally forwarded to the target using the default behavior.

Can a proxy observe every change to its target?
No. It intercepts operations made through the proxy. Direct changes through the target or another reference do not pass through that proxy.

Why use Reflect inside a trap?
It provides a direct way to forward the operation to the target while preserving details such as the receiver and returning the operation’s success result.

Can a proxy be used with any object?
Many objects can be proxied, but built-ins with special internal state may not work when methods receive the proxy as this. Test the specific object and method behavior rather than assuming transparent compatibility.

Is a proxy the same object as its target?
No. The proxy wraps the target but has a distinct identity. Strict equality and identity-based collections distinguish between them.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.