The Codex / JavaScript / Proxy & Reflect

Proxy & Reflect

Intercept and customise fundamental object operations — get, set, delete, function calls, and more — with Proxy traps and the Reflect API.

What it is, in plain English: A Proxy wraps an object and lets you intercept (trap) fundamental operations on it: reading a property, writing a property, deleting a property, calling a function, and more. For every trap you define, the corresponding operation is redirected through your handler. The Reflect API provides the default behaviour for each operation — so you can intercept, do something extra, and then delegate the real work to Reflect.

Wrapping objects to intercept their operations

A Proxy sits in front of an object. Every time code tries to read, write, or delete a property on the proxy, your handler function gets called first — giving you a chance to validate, log, transform, or block the operation.

Creating a Proxy

// new Proxy(target, handler)
// target  — the real object being wrapped
// handler — an object with "trap" methods

const person = { name: "Alice", age: 30 };

const proxy = new Proxy(person, {
  get(target, key) {
    // Called every time proxy.someProperty is read
    console.log(`→ Reading "${key}"`);
    return target[key];  // return the actual value
  },
  set(target, key, value) {
    // Called every time proxy.someProperty = value
    console.log(`→ Writing "${key}" = ${value}`);
    target[key] = value;
    return true;   // must return true to indicate success
  }
});

proxy.name;         // logs: → Reading "name"
proxy.age = 31;     // logs: → Writing "age" = 31

The most useful trap: validation

function createValidatedUser(data) {
  return new Proxy(data, {
    set(target, key, value) {
      if (key === "age") {
        if (typeof value !== "number") throw new TypeError("age must be a number");
        if (value < 0 || value > 150)  throw new RangeError("age must be 0–150");
      }
      if (key === "email" && !value.includes("@")) {
        throw new Error("Invalid email address");
      }
      target[key] = value;
      return true;
    }
  });
}

const user = createValidatedUser({ name: "Bob" });
user.name  = "Bob";         // fine
user.age   = 25;            // fine
// user.age   = -5;         // throws: age must be 0–150
// user.email = "notvalid"; // throws: Invalid email address

Reflect — the default implementation

Inside a trap, use Reflect to perform the operation normally. This is cleaner than manually doing target[key] because Reflect handles edge cases correctly (inherited properties, getters/setters, etc.).

const handler = {
  get(target, key, receiver) {
    console.log(`Reading "${key}"`);
    return Reflect.get(target, key, receiver);  // same as: target[key]
  },
  set(target, key, value, receiver) {
    console.log(`Writing "${key}"`);
    return Reflect.set(target, key, value, receiver);  // same as: target[key] = value
  }
};

const proxy = new Proxy({ x: 1 }, handler);
proxy.x;      // "Reading x"
proxy.y = 2;  // "Writing y"
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.