Gyaan

Proxy & Reflect

advanced Proxy Reflect metaprogramming ES6

Proxy lets us wrap an object and intercept operations on it — like reading a property, setting a value, checking if a key exists, etc. Think of it as putting a guard in front of an object that can inspect and modify every interaction.

How Proxy Works

A Proxy takes two arguments: the target object and a handler with traps (functions that intercept operations).

const user = { name: 'Manish', age: 25 };

const proxy = new Proxy(user, {
  get(target, prop) {
    console.log(`Reading "${prop}"`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Setting "${prop}" to ${value}`);
    target[prop] = value;
    return true; // must return true for success
  }
});

proxy.name;          // logs: Reading "name" → 'Manish'
proxy.age = 26;      // logs: Setting "age" to 26

Common Handler Traps

Here are the traps we use most often:

  • get(target, prop) — reading a property
  • set(target, prop, value) — writing a property
  • has(target, prop) — the in operator
  • deleteProperty(target, prop) — the delete operator
  • apply(target, thisArg, args) — calling a function

Use Case: Validation Proxy

One of the most practical uses — we can validate values before they’re set on an object.

const validator = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (typeof value !== 'number') throw TypeError('Age must be a number');
      if (value < 0 || value > 150) throw RangeError('Age must be 0-150');
    }
    target[prop] = value;
    return true;
  }
};

const person = new Proxy({}, validator);
person.name = 'Manish';  // works fine
person.age = 25;          // works fine
// person.age = -5;       // RangeError: Age must be 0-150
// person.age = 'old';    // TypeError: Age must be a number

Use Case: Logging Proxy

We can wrap any object to log every access — useful for debugging.

function withLogging(obj) {
  return new Proxy(obj, {
    get(target, prop) {
      console.log(`[GET] ${prop} → ${target[prop]}`);
      return target[prop];
    },
    set(target, prop, value) {
      console.log(`[SET] ${prop} = ${value}`);
      target[prop] = value;
      return true;
    }
  });
}

const config = withLogging({ debug: false, port: 3000 });
config.debug;         // [GET] debug → false
config.port = 8080;   // [SET] port = 8080

Reflect

Reflect is a built-in object that provides methods matching every Proxy trap. Instead of directly doing target[prop], we can use Reflect.get(target, prop) — it’s cleaner and always returns the correct default behavior.

const proxy = new Proxy(user, {
  get(target, prop, receiver) {
    console.log(`Accessing ${prop}`);
    return Reflect.get(target, prop, receiver); // proper default
  },
  set(target, prop, value, receiver) {
    console.log(`Setting ${prop}`);
    return Reflect.set(target, prop, value, receiver);
  },
  has(target, prop) {
    console.log(`Checking if "${prop}" exists`);
    return Reflect.has(target, prop);
  }
});

Why use Reflect instead of target[prop]? Because Reflect methods return success/failure booleans, handle edge cases with inheritance correctly (via the receiver parameter), and map 1-to-1 with every Proxy trap.

Real-World Usage

This isn’t just a theoretical concept. Vue.js 3 uses Proxy to power its reactivity system. When we change a reactive property, Vue’s Proxy trap detects the change and triggers a re-render. Before Vue 3, they used Object.defineProperty which had limitations (couldn’t detect new property additions or array index changes).

In simple language, Proxy is like a security guard for objects — every time someone tries to read, write, or check something on the object, the guard can inspect it, modify it, or block it. Reflect just gives us a clean way to do the “normal” thing inside those guard functions.