JavaScript Proxy and Reflect

Proxy and Reflect are advanced ES6 features that allow interception and customization of fundamental JavaScript operations — such as reading a property, writing a value, calling a function, or checking if a key exists. They are powerful tools for validation, logging, data binding, and building reactive frameworks.

What is a Proxy?

A Proxy wraps an object and intercepts operations performed on it. Every time code tries to read, write, delete, or call something on the proxied object, a trap (handler function) can be triggered.

Real-life analogy: A Proxy is like a secretary. When someone wants to talk to a manager (target object), they go through the secretary (proxy) first. The secretary can allow, deny, or modify the request.

Syntax:

let proxy = new Proxy(target, handler);
  • target — the original object being wrapped
  • handler — an object whose methods (traps) intercept operations

Basic Proxy with get Trap

The get trap intercepts property access.

let user = { name: "Kavita", age: 28 };

let proxy = new Proxy(user, {
  get(target, property) {
    console.log(`Getting property: ${property}`);
    return property in target ? target[property] : "Property not found";
  }
});

console.log(proxy.name);    // Getting property: name → Kavita
console.log(proxy.age);     // Getting property: age  → 28
console.log(proxy.email);   // Getting property: email → Property not found

set Trap — Intercept Property Assignment

The set trap intercepts when a property value is being written. This is great for validation.

let person = {};

let proxy = new Proxy(person, {
  set(target, property, value) {
    if (property === "age") {
      if (typeof value !== "number") {
        throw new TypeError("Age must be a number.");
      }
      if (value < 0 || value > 120) {
        throw new RangeError("Age must be between 0 and 120.");
      }
    }
    target[property] = value;
    return true;  // Must return true to indicate success
  }
});

proxy.name = "Suresh";
proxy.age  = 30;         // Passes validation
console.log(person);     // { name: "Suresh", age: 30 }

// proxy.age = -5;       // RangeError: Age must be between 0 and 120.
// proxy.age = "thirty"; // TypeError: Age must be a number.

has Trap — Intercept the in Operator

let range = { min: 1, max: 100 };

let rangeProxy = new Proxy(range, {
  has(target, key) {
    // Instead of checking for a property, check if a number is in range
    let num = Number(key);
    return num >= target.min && num <= target.max;
  }
});

console.log(50 in rangeProxy);   // true  (50 is in range 1–100)
console.log(150 in rangeProxy);  // false (150 is outside range)

deleteProperty Trap

let config = { theme: "dark", lang: "en", version: "2.0" };

let safeConfig = new Proxy(config, {
  deleteProperty(target, property) {
    if (property === "version") {
      throw new Error("Cannot delete 'version' — it is protected.");
    }
    delete target[property];
    return true;
  }
});

delete safeConfig.lang;      // Works fine
// delete safeConfig.version; // Error: Cannot delete 'version'
console.log(config);          // { theme: "dark", version: "2.0" }

apply Trap — Intercept Function Calls

function add(a, b) {
  return a + b;
}

let trackedAdd = new Proxy(add, {
  apply(target, thisArg, args) {
    console.log(`add(${args.join(", ")}) called`);
    let result = target.apply(thisArg, args);
    console.log(`Result: ${result}`);
    return result;
  }
});

trackedAdd(3, 7);
// add(3, 7) called
// Result: 10

Revocable Proxy

A revocable proxy can be deactivated. Once revoked, any access to the proxy throws an error.

let { proxy, revoke } = Proxy.revocable({ name: "Tanvi" }, {});

console.log(proxy.name); // Tanvi

revoke();  // Deactivate the proxy

// proxy.name;  // TypeError: Cannot perform 'get' on a proxy that has been revoked

What is Reflect?

The Reflect object provides static methods that mirror the default behavior of proxy traps. It is often used inside proxy handlers to perform the default operation while adding custom logic.

Reflect Methods Mirror Proxy Traps

Proxy TrapReflect Equivalent
getReflect.get(target, prop)
setReflect.set(target, prop, value)
deletePropertyReflect.deleteProperty(target, prop)
hasReflect.has(target, prop)
applyReflect.apply(fn, thisArg, args)
constructReflect.construct(target, args)

Using Reflect in Proxy Traps

let target = { score: 80 };

let proxy = new Proxy(target, {
  get(target, property, receiver) {
    console.log(`Reading: ${property}`);
    return Reflect.get(target, property, receiver); // Default behavior
  },
  set(target, property, value, receiver) {
    console.log(`Writing: ${property} = ${value}`);
    return Reflect.set(target, property, value, receiver); // Default behavior
  }
});

proxy.score;         // Reading: score
proxy.score = 95;    // Writing: score = 95
console.log(target.score); // 95

Practical Example — Observable Object

A Proxy can notify listeners whenever a property changes — this is the foundation of reactive data systems used in frameworks like Vue.js.

function makeObservable(obj, onChange) {
  return new Proxy(obj, {
    set(target, property, value) {
      let oldValue = target[property];
      target[property] = value;
      onChange(property, oldValue, value);
      return true;
    }
  });
}

let settings = makeObservable({ theme: "light", fontSize: 14 }, (key, oldVal, newVal) => {
  console.log(`"${key}" changed from "${oldVal}" to "${newVal}"`);
});

settings.theme    = "dark";    // "theme" changed from "light" to "dark"
settings.fontSize = 18;        // "fontSize" changed from "14" to "18"

Key Points to Remember

  • A Proxy wraps an object and intercepts operations via trap functions defined in a handler
  • The get trap intercepts property reads; the set trap intercepts property writes
  • The apply trap intercepts function calls; the has trap intercepts the in operator
  • Always return true from a set trap to indicate a successful assignment
  • Revocable proxies can be deactivated to cut off access to the underlying object
  • Reflect provides methods that mirror each proxy trap's default behavior
  • Using Reflect inside proxy handlers ensures correct default behavior while adding custom logic
  • Proxies are the foundation of reactivity systems in modern frameworks

Leave a Comment

Your email address will not be published. Required fields are marked *