Step 1 of 5 · Reading · ~3 min
Learn
Proxy and Memory
Proxy and Reflect
A Proxy wraps an object and lets your code decide what happens when someone reads, writes, deletes or lists its properties. Reflect is the toolbox for doing the default thing from inside such a decision. By the end you can write a proxy that guards, logs or reshapes an object without breaking getters or strict-mode callers.
Target, handler, traps
new Proxy(target, handler) returns an object that forwards every operation to target, except the ones handler overrides. Each override is a trap, a method named after the operation:
| Operation | Trap |
|---|---|
p.x, p[x] | get(target, key, receiver) |
p.x = v | set(target, key, value, receiver) |
key in p | has(target, key) |
delete p.x | deleteProperty(target, key) |
Object.keys(p), for...in, JSON.stringify(p) | ownKeys(target) |
p(...) on a function | apply(target, thisArg, args) |
new p(...) | construct(target, args, newTarget) |
A handler with no traps is a transparent pass-through. Keys arrive as strings or symbols, so a trap that calls string methods on key must check typeof key first.
Reflect does the default, correctly
Reflect has one function per trap (Reflect.get, Reflect.set, Reflect.has, Reflect.ownKeys, ...) that performs the normal operation. Prefer it to target[key] because of the receiver: the object the operation was started on. Look at what a getter does with this:
A set trap must say whether it worked
set returns a boolean. Returning false (or nothing) means "refused". In sloppy mode the assignment then silently does nothing; in strict mode it throws a TypeError. Class bodies and ES modules are always strict, so a missing return true often shows up only after a refactor.
Invariants: a trap cannot lie about everything
The engine checks traps against the target. If the target has a non-configurable, non-writable property id with value 1, a get trap that answers 2 throws a TypeError, and ownKeys must still list every non-configurable key. Hiding ordinary configurable keys is allowed:
Limits worth knowing
- A proxy is a different object:
new Proxy(o, {}) !== o, so identity checks andWeakMapkeys see two objects. - Built-ins with internal slots (
Map,Set,Date) reject a proxy as receiver:new Proxy(new Map(), {}).set('a', 1)throwsTypeError. Return methods bound to the target fromget. Proxy.revocable(target, handler)gives{ proxy, revoke }; afterrevoke()every operation on the proxy throwsTypeError.- Every access pays for a function call. Use proxies at boundaries (validation, reactivity), not in hot inner loops.
Common mistakes
- Reading
proxy[key]inside a trap on the same proxy. It re-enters the trap forever; usetargetorReflect. - Forgetting to return a value from
get, so every property reads asundefined. - Trapping
getand callingkey.startsWith(...)when the engine passes a symbol such asSymbol.toPrimitive.
Your exercise
You will build a guarded settings store from five traps. The usual slip is set: it must return false for a refusal and true for a success, and the starter runs in strict mode, so a wrong answer is visible as a TypeError.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…