JS Proxy Pattern
Intercept and control object property access with ES6 Proxy
When to Use
- You need to validate, log, or transform property reads/writes without changing the target object
- Implementing reactive data systems (watching object mutations)
- Adding access control or lazy initialization to expensive objects
- Building observable models for state management
Instructions
- Create a handler object with trap methods (
get, set, deleteProperty, etc.).
- Wrap the target object:
const proxy = new Proxy(target, handler).
- In
set traps, always call Reflect.set(target, prop, value) to apply the change and return its boolean result.
- In
get traps, use Reflect.get(target, prop, receiver) to preserve prototype chain behavior.
- Use
Reflect methods in traps — they mirror the Proxy trap API and ensure correct semantics.
const validator = {
set(target, prop, value) {
if (prop === 'age') {
if (typeof value !== 'number' || value < 0) {
throw new TypeError('Age must be a non-negative number');
}
}
return Reflect.set(target, prop, value);
},
};
const person = new Proxy({}, validator);
person.age = 30; // OK
person.age = -1; // Throws TypeError
- Avoid deeply nested Proxy wrapping — it compounds performance overhead on every property access.
Details
ES6 Proxy gives you a meta-programming hook at the object level. Traps intercept fundamental operations: get, set, has, deleteProperty, apply (for functions), and construct (for classes).
Trade-offs:
- Proxy adds overhead per property access — avoid on hot paths (tight loops, rendering cycles)
- Proxied objects are not equal to their targets (
proxy !== target) — equality checks must use the target
- Proxies are not serializable —
JSON.stringify(proxy) serializes the underlying target, which may surprise callers
- Debugging is harder — the DevTools shows the proxy wrapper, not the target directly
When NOT to use:
- For simple validation — just write a setter method or use a class
- For immutability at scale —
Object.freeze() is simpler and has no runtime overhead per access
- When you need ES5 compatibility — Proxy cannot be polyfilled
Related patterns:
- Observer Pattern — Proxy can power reactive observation of object mutations
- Singleton Pattern — a Proxy can wrap a singleton to control access
Source
https://patterns.dev/javascript/proxy-pattern
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.
1---2name: js-proxy-pattern3description: JS Proxy Pattern4---5# JS Proxy Pattern67> Intercept and control object property access with ES6 Proxy89## When to Use1011- You need to validate, log, or transform property reads/writes without changing the target object12- Implementing reactive data systems (watching object mutations)13- Adding access control or lazy initialization to expensive objects14- Building observable models for state management1516## Instructions17181. Create a handler object with trap methods (`get`, `set`, `deleteProperty`, etc.).192. Wrap the target object: `const proxy = new Proxy(target, handler)`.203. In `set` traps, always call `Reflect.set(target, prop, value)` to apply the change and return its boolean result.214. In `get` traps, use `Reflect.get(target, prop, receiver)` to preserve prototype chain behavior.225. Use `Reflect` methods in traps — they mirror the Proxy trap API and ensure correct semantics.2324```javascript25const validator = {26 set(target, prop, value) {27 if (prop === 'age') {28 if (typeof value !== 'number' || value < 0) {29 throw new TypeError('Age must be a non-negative number');30 }31 }32 return Reflect.set(target, prop, value);33 },34};3536const person = new Proxy({}, validator);37person.age = 30; // OK38person.age = -1; // Throws TypeError39```40416. Avoid deeply nested Proxy wrapping — it compounds performance overhead on every property access.4243## Details4445ES6 `Proxy` gives you a meta-programming hook at the object level. Traps intercept fundamental operations: get, set, has, deleteProperty, apply (for functions), and construct (for classes).4647**Trade-offs:**4849- Proxy adds overhead per property access — avoid on hot paths (tight loops, rendering cycles)50- Proxied objects are not equal to their targets (`proxy !== target`) — equality checks must use the target51- Proxies are not serializable — `JSON.stringify(proxy)` serializes the underlying target, which may surprise callers52- Debugging is harder — the DevTools shows the proxy wrapper, not the target directly5354**When NOT to use:**5556- For simple validation — just write a setter method or use a class57- For immutability at scale — `Object.freeze()` is simpler and has no runtime overhead per access58- When you need ES5 compatibility — Proxy cannot be polyfilled5960**Related patterns:**6162- Observer Pattern — Proxy can power reactive observation of object mutations63- Singleton Pattern — a Proxy can wrap a singleton to control access6465## Source6667https://patterns.dev/javascript/proxy-pattern6869## Process70711. Read the instructions and examples in this document.722. Apply the patterns to your implementation, adapting to your specific context.733. Verify your implementation against the details and edge cases listed above.7475## Harness Integration7677- **Type:** knowledge — this skill is a reference document, not a procedural workflow.78- **No tools or state** — consumed as context by other skills and agents.7980## Success Criteria8182- The patterns described in this document are applied correctly in the implementation.83- Edge cases and anti-patterns listed in this document are avoided.