Proxy & Reflect
Proxy wraps an object and intercepts fundamental operations (get, set, delete, apply, construct, etc.) via handler traps. Reflect provides the same operations as methods, making it easy to forward to the target.
Proxy is a bodyguard standing in front of an object. Every time someone tries to get, set, or delete a property, the bodyguard intercepts and decides what to do. Reflect is the bodyguard's earpiece to relay the original request faithfully.
Common Proxy use cases: validation (throw in set trap if value invalid), default values (return fallback in get trap), reactivity (trigger effects on set), logging/debugging, mocking in tests. Proxy is transparent — typeof, instanceof, and === work on the proxy as if it's the target for most uses. Reflect methods mirror Proxy traps 1:1, making handler code cleaner: return Reflect.set(target, key, value, receiver) in a set trap forwards correctly without repeating logic. The receiver parameter in get/set traps is critical for prototype-based access — always forward it.
Vue 3's reactivity system uses Proxy instead of Object.defineProperty (Vue 2) to intercept array mutations and new property additions, which defineProperty couldn't detect. Proxies can wrap functions (apply trap) to create middleware or memoization wrappers. Proxy invariants enforce consistency — some traps have rules that prevent the proxy from lying (e.g., a get trap cannot return a different value for a non-writable, non-configurable own property). Revocable proxies (Proxy.revocable()) can be disabled: proxy.revoke() makes all further trap calls throw — useful for capability-based security patterns.
Proxy intercepts fundamental object operations with handler traps, enabling meta-programming: validation, reactivity, logging, and virtualization. Always use Reflect in trap bodies to forward operations correctly, especially with inheritance (the receiver parameter preserves correct this for prototype getters/setters). Vue 3's reactivity is the canonical production Proxy use case.
Proxies cannot be transparently used with built-in types that rely on internal slots — for example, a Proxy around a Map won't work because Map methods check for the [[MapData]] internal slot on the actual this, not the proxy. You must forward the 'this' correctly using the get trap: get(target, key) { const val = target[key]; return typeof val === 'function' ? val.bind(target) : val; }.