Proxy and Reflect
Intercept object operations with Proxy traps, forward correctly via Reflect, and know performance and invariant limits.
- 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
Objectmethods 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
- Getters and Setters
- Object.create and the prototype chain
- Symbols in JavaScript
- Private Class Fields
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.