Skip to main content

Menu

System onlineAI Tutorials / 2026

AI Tutorials
Knowledge search

Recent · Popular · Tutorials

  • dart3 tutorials
  • java1 tutorial
  • Javascript3 tutorials
  • Node.js Introduction2 tutorials
  • dartdart · General · In depth
  • JavascriptJavascript · Advanced · In depth
  • JavascriptJavascript · Advanced · Mid
  • JavascriptJavascript · Advanced · Short

Find a tutorial

Tell the assistant what you want to learn. It suggests published chapters.

For example: “I want to learn Rust ownership from the start.” The conversation is kept only in this tab.

0 / 2,000 characters

Enter to send, Shift+Enter for a new line. Do not include personal information.

Chapter 22

Proxy and Reflect

This chapter assumes you already understand objects, property descriptors (from the Prototype Chain chapter), and `this` binding. `Proxy` and `Reflect` are the two primitives that make metaprogramming — programs that inspect and modify other programs' behavior — possible in JavaScript. They rarely a
Level: AdvancedLength: In depth4 min read

Read at another depth

Proxy and Reflect

This chapter assumes you already understand objects, property descriptors (from the Prototype Chain chapter), and this binding. Proxy and Reflect are the two primitives that make metaprogramming — programs that inspect and modify other programs' behavior — possible in JavaScript. They rarely appear in application code, but they underpin frameworks you use daily: Vue's reactivity, MobX, test mocks, and ORM lazy-loading all rely on them.

A Proxy wraps a target object and intercepts fundamental operations — property read, write, in, delete, function calls, iteration — via optional traps. Reflect mirrors the same operations as plain functions, providing the canonical default implementation.

Code example in javascript
const target = { a: 1 };
const proxy = new Proxy(target, {
  get(target, prop, receiver) {
    console.log(`read ${String(prop)}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`write ${String(prop)} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
});

proxy.a;        // read a
proxy.b = 2;    // write b = 2
console.log(target.b); // 2 — the target actually changed

The receiver argument matters when getters and setters are involved: passing it (rather than target) preserves this semantics for accessors defined on the prototype chain. This is the single most common correctness detail people get wrong, and we return to it under Failure modes.

Why Reflect exists

Before Reflect, overriding one operation meant reimplementing the others by hand. Reflect gives you the default behavior of every trap as a first-class function:

TrapReflect equivalent
`get``Reflect.get(target, prop, receiver)`
`set``Reflect.set(target, prop, value, receiver)`
`has``Reflect.has(target, prop)`
`deleteProperty``Reflect.deleteProperty(target, prop)`
`apply``Reflect.apply(fn, thisArg, args)`
`ownKeys``Reflect.ownKeys(target)`

The rule of thumb: inside a trap, delegate to the corresponding Reflect method with the arguments you received. Only deviate deliberately.

Trade offs

Proxies trade transparency for power, and the costs are real.

  • Performance. Every intercepted operation goes through an internal trap dispatch. A get trap on a hot path can be 10–100× slower than a plain property read, depending on engine and trap complexity. Browsers and V8 optimize monomorphic direct reads aggressively; proxies defeat that. Avoid proxies on hot data structures (rendering loops, tight numeric computation).
  • Invariants cost correctness freedom. Certain lies are forbidden: a proxy whose target has a non-configurable, non-writable data property cannot have its get trap return a different value; ownKeys must include all non-configurable own keys of the target; the result of getOwnPropertyDescriptor must be consistent with the target. The engine throws a TypeError when a trap violates these. This protects ecosystem code from impossible objects, but it constrains what virtual objects you can build — pick a target shape that matches your fiction.
  • Identity leaks. A proxy is not its target. ===, Map/Set keys, WeakMap keys, and Array.isArray (which special-cases proxies of arrays correctly, but only by spec) all treat them distinctly. If code compares objects by identity, wrapping them changes behavior.
  • Debuggability. Stack traces through traps, console.log of proxies, and devtools inspection can be confusing. Name your handler functions.

When to reach for a proxy

Use caseFit
Reactive frameworks (deep observation)Excellent — this is the design center
Logging/metrics on an API surfaceGood
Validation on writeGood
Virtualizing huge/fetched-on-demand objectsGood, with care
Performance-critical inner loopsPoor — use plain objects
Deeply cloning-on-access object graphsPoor — identity semantics break

Failure modes

  1. Dropping receiver. If a getter on the prototype chain reads another property via this, forwarding get with target instead of receiver breaks the chain: this becomes the target, not the proxy, and subsequent reads bypass your traps. Always forward receiver.
  2. Non-invariant traps returning inconsistent data. Returning undefined from getOwnPropertyDescriptor for a property you also report in ownKeys throws. Similarly, set must return true on success in strict mode — returning false or undefined (falsy) surfaces as a TypeError at the assignment site, far from the trap, which makes it painful to diagnose.
  3. Forgetting that internal slots are invisible to proxies. A Date, Map, Set, Promise, or RegExp stores its state in internal slots, not properties. A proxy of a Date cannot work — proxy.getTime() throws because this is the proxy, which lacks the slot.
Code example in javascript
const d = new Proxy(new Date(), {});
d.getTime(); // TypeError: this is not a Date object

Workarounds: bind methods up front (new Proxy(target, { get: (t, p) => Reflect.get(t, p).bind?.(t) ?? Reflect.get(t, p) })) or store state in ordinary properties instead.

  1. Revoked proxies are forever. Proxy.revocable gives you a revoke() function; after it runs, every operation on the proxy throws TypeError. Useful for capability-based access control, fatal if revoked while an async callback still holds the proxy.
  2. Recursive wrapping. Naively wrapping nested objects on get creates a new proxy per read, so deep.a === deep.a is false and Set membership fails. Cache wrappers per target (typically in a WeakMap).
Code example in javascript
const wrappers = new WeakMap();
function deepProxy(target, handler) {
  if (wrappers.has(target)) return wrappers.get(target);
  const proxy = new Proxy(target, handler);
  wrappers.set(target, proxy);
  return proxy;
}

Internals

At the spec level, every ordinary-object operation is defined as an abstract operation (e.g., [[Get]], [[Set]], [[OwnPropertyKeys]]) that takes a receiver. Proxy implements the same internal methods, but each one checks whether the handler defines the corresponding trap; if not, or if the trap returns a non-object in places where one is required, it falls back to the default behavior applied to the target.

The receiver parameter exists precisely so Reflect.get(target, key, receiver) can be the exact fallback the spec performs. That symmetry is why "delegate with the same arguments" is a complete, correct default.

caption: Proxy get flow with trap dispatch and fallback

alt: A flowchart showing a property access entering the proxy's internal Get method, branching to either the handler's get trap or a fallback to the target, with an invariant check that can raise a TypeError.

Engines implement trap dispatch as a fast path: if the handler is a plain object with no traps (a "revocable no-op" or empty handler), V8 can inline the fallback. Adding traps deoptimizes the target's hidden class transitions around the proxy boundary, which is why wrapping inside a library's public API — not its internals — is the standard practice.

A final design criterion: proxies intercept fundamental operations, not syntactic forms. proxy.x, proxy['x'], and destructuring const { x } = proxy all hit get; there is no trap for "dot access specifically". Choose traps by operation, never by syntax.

Exercise

Why must a get trap forward the receiver argument to Reflect.get instead of passing target, and what breaks if it doesn't?

Show answer

If the target (or its prototype chain) has accessor properties that read other properties via this, this must be the original proxy so those reads re-enter the traps. Passing target binds this to the raw target, so subsequent property reads bypass the proxy entirely and go unobserved (or return stale values in a reactivity system).

Exercise

You wrap a Map in a proxy and map.get('k') throws. Why, and what are two fixes?

Show answer

Map.prototype.get reads internal slots on this, and this is the proxy, which has no slots. Fix 1: return bound methods from the get trap (Reflect.get(...).bind(target)). Fix 2: don't proxy the Map; expose it behind an API object with ordinary-property state, or trap at a wrapper level above the Map.

Spotted a mistake, or code that does not work?

Report an issue

Chapter: Proxy and Reflect

0 / 2,000 characters

Plain text, for example: the second code sample does not compile. Do not include personal information.

You can't do that