Master

Proxy and Reflect

Intercept object operations with Proxy and Reflect for powerful metaprogramming.

What you will see

revoked

About 2 min of reading

Try It

Wrap an object in a Proxy to intercept reads and writes, compute hidden properties, and build a proxy that can be revoked. Reflect keeps each interception faithful to native semantics.

1

Wrap an object so any attempt to set age outside 0-150 is thrown. Accept a valid value, then trap an invalid one in try/catch and print what it reported.

const target = { age: 30 }; const proxy = new Proxy(target, { set(obj, prop, value) { if (prop === 'age' && (value < 0 || value > 150)) { throw new Error(`Invalid ${prop}: ${value}`); } obj[prop] = value; return true; } }); proxy.age = 31; try { proxy.age = 999; } catch (e) { console.log("blocked:", e.message); } console.log(proxy.age);

Before you run it, say out loud what it will print. Then open the answer.

What it prints
blocked: Invalid age: 999 31
A Proxy intercepts get/set/deleteProperty and more. Traps must respect target invariants or the engine throws.
2

Synthesize a property that does not exist on the target by returning a computed value from the get trap. Real properties still pass through to the target.

const person = { firstName: 'Ada', lastName: 'Lovelace' }; const full = new Proxy(person, { get(t, prop) { if (prop === 'fullName') { return `${t.firstName} ${t.lastName}`; } return t[prop]; } }); console.log(full.fullName); console.log(full.firstName);

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Ada Lovelace Ada
A get trap can synthesize computed or virtual properties. Use Reflect for the pass-through case to keep native semantics intact.
3

Create a revocable proxy and then disable it permanently. After revoking, any access throws, which is handy for cleanup and test doubles.

const target = { x: 1 }; const { proxy, revoke } = Proxy.revocable(target, { get(t, prop) { return t[prop]; } }); revoke(); try { proxy.x; } catch (e) { console.log("revoked"); }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
revoked
`Proxy.revocable` returns a proxy and a revoke function. Calling revoke permanently disables the proxy; accessing it afterwards throws.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A Proxy wraps a target; operations pass through traps that can validate, transform, or intercept before Reflect forwards them.
1 Operation on the proxy
↓
2 Proxy invokes the matching trap
↓
3 Trap validates or transforms the input
↓
4 Reflect forwards to the target -> returns result
Why this works
The full set of traps

Proxy can intercept many internal operations:

new Proxy(target, {
  get, set, has, apply, construct,
  getPrototypeOf, setPrototypeOf,
  defineProperty, deleteProperty,
  ownKeys, getOwnPropertyDescriptor
});

Each trap corresponds to an ECMAScript internal method. apply intercepts function calls; construct intercepts new; ownKeys intercepts Object.keys. Reflect provides matching methods for each trap.

Invariants and Revocable proxies

Proxies must respect target invariants, or the engine throws:

const frozen = Object.freeze({ x: 1 });
const p = new Proxy(frozen, {
  set() { return true; }   // would violate frozen invariant
});
p.x = 2;   // TypeError: property is frozen

Proxy.revocable creates a proxy that can be permanently disabled:

const { proxy, revoke } = Proxy.revocable(target, { get(t, prop) { return t[prop]; } });
revoke();
proxy.x;   // TypeError: proxy revoked

Use revocable proxies for resource cleanup and test doubles.

Examples

A validation proxy

Intercepting gets and sets

const target = { age: 30 };
const proxy = new Proxy(target, {
  set(obj, prop, value) {
    if (prop === 'age' && (value < 0 || value > 150)) {
      throw new Error(`Invalid ${prop}: ${value}`);
    }
    obj[prop] = value;
    return true;
  }
});
proxy.age = 31;       // OK
proxy.age = 999;      // Error: Invalid age

A Proxy wraps another object and intercepts operations like get/set/deleteProperty. Reflect mirrors these operations as functions, keeping semantics consistent.

Reflect for correct semantics

Matching the native behavior

const proxy = new Proxy(obj, {
  get(t, prop) {
    return Reflect.get(t, prop);   // same as t[prop]
  },
  has(t, prop) {
    return Reflect.has(t, prop);
  }
});

Reflect provides the same operations as operators (get, set, has, deleteProperty, etc.) as callable functions. Using them inside a Proxy keeps the interception faithful to native semantics.

Computing properties

Deriving values

const person = { firstName: 'Ada', lastName: 'Lovelace' };
const full = new Proxy(person, {
  get(t, prop) {
    if (prop === 'fullName') {
      return `${t.firstName} ${t.lastName}`;
    }
    return Reflect.get(t, prop);
  }
});
console.log(full.fullName);   // Ada Lovelace

A Proxy can synthesize properties that do not exist on the target. This is useful for computed fields, virtual attributes, and lazy-loaded data.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Modules and ES Modules
Previous Lesson
Modules and ES Modules
Next Lesson
Decorators and Metaprogramming
Decorators and Metaprogramming →