ESC

Type to search the knowledge base.

Proxy and Reflect

Intercept object operations with Proxy traps, forward correctly via Reflect, and know performance and invariant limits.

advanced3 min read
  • javascript
  • proxy-and

A Proxy wraps a target object and intercepts fundamental operations: get, set, has, delete, apply, construct, and more. Reflect exposes those same operations as functions so traps can forward cleanly with correct return values and this.

const user = { name: 'Ada', age: 36 };

const proxy = new Proxy(user, {
  get(target, prop, receiver) {
    console.log('get', prop);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    if (prop === 'age' && typeof value !== 'number') {
      throw new TypeError('age must be number');
    }
    return Reflect.set(target, prop, value, receiver);
  },
});

proxy.name;    // logs get name
proxy.age = 37;

Why Reflect

// Fragile forward:
return target[prop]; // wrong this for getters; ignores receiver

// Correct:
return Reflect.get(target, prop, receiver);

receiver matters when the property is an accessor on the prototype and you proxy instances — inheritance-aware get/set needs it. Reflect methods return booleans for success on set/delete where traps must return booleans (strict mode throws on false).

Useful traps

Trap Intercepts
get / set property read/write
has in operator
deleteProperty delete
ownKeys Object.keys, for...in related enumeration paths
apply function call when target is function
construct new
function trace(fn) {
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      console.log('call', args);
      return Reflect.apply(target, thisArg, args);
    },
  });
}

Reactive / validation sketch

function observable(obj, onChange) {
  return new Proxy(obj, {
    set(t, p, v, r) {
      const ok = Reflect.set(t, p, v, r);
      if (ok) onChange(p, v);
      return ok;
    },
  });
}

Libraries (Vue 3 reactivity historically, Immer variants, validation layers) lean on Proxy. Note: proxies are not transparent for identity — proxy !== target, and Map keyed by target won’t find proxy.

Invariants (you can’t break the language)

The engine enforces invariants. Example: if a property is non-configurable non-writable on the target, the proxy can’t report a different value from get. Violations throw TypeError. Read the spec list when writing exotic proxies.

Performance and debugging

  • Proxy access is slower than plain objects; don’t proxy hot numeric loops
  • Stack traces and DevTools can be noisier
  • Object methods that don’t go through traps? Most do for ordinary objects; some internal slots (e.g. Date) aren’t fully trapable

Negative array index example

function niceArray(arr) {
  return new Proxy(arr, {
    get(t, p, r) {
      if (typeof p === 'string' && /^-?\d+$/.test(p)) {
        let i = Number(p);
        if (i < 0) i = t.length + i;
        return t[i];
      }
      return Reflect.get(t, p, r);
    },
  });
}
niceArray([1, 2, 3])[-1]; // 3

Interview answer (out loud)

“Proxy intercepts operations on an object via traps. I forward with Reflect so receivers and boolean results stay correct. Good for validation, logging, and reactivity. Cost is performance and identity — proxy isn’t the target — and traps must respect invariants.”

Further reading

Related guides