Suggest an editImprove this articleRefine the answer for “What is Proxy Object in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Proxy object** is a constructor that wraps a target object and intercepts its operations through trap methods defined in a handler. ```javascript const proxy = new Proxy({ name: 'Alice' }, { get(target, prop, receiver) { console.log(`Reading ${prop}`); return Reflect.get(target, prop, receiver); } }); proxy.name; // Reading name -> Alice ``` **Key point:** unlike `Object.defineProperty`, Proxy intercepts all operations on any property, including ones that don't exist yet.Shown above the full answer for quick recall.Answer (EN)Image**Proxy object** is a built-in JavaScript constructor that wraps a target object and intercepts its fundamental operations through a handler containing optional trap methods. ## Theory ### TL;DR - Think of it as a security checkpoint: every read, write, or delete on the proxy passes through your trap function before (or instead of) reaching the target - Main split from `Object.defineProperty`: Proxy traps ALL operations on ANY property, including ones that don't exist yet; defineProperty only covers statically known keys - `Proxy.revocable()` returns `{proxy, revoke}`; call `revoke()` and every subsequent operation on that proxy throws permanently - V8 benchmarks put Proxy at 2-5x slower than direct property access; skip it for tight loops - Always use `Reflect` methods inside traps to get default behavior with the correct `receiver` context ### Quick example ```javascript const user = { name: 'Alice', age: 25 }; const proxy = new Proxy(user, { get(target, prop, receiver) { console.log(`Accessing ${prop}`); return Reflect.get(target, prop, receiver); // preserve receiver for correct `this` } }); console.log(proxy.name); // Accessing name -> Alice proxy.age; // Accessing age ``` The third argument `receiver` matters when methods use `this`. Without it, calls through an inherited prototype chain break in ways that are hard to debug. ### Proxy vs Object.defineProperty `Object.defineProperty` patches one known property at definition time. It cannot intercept the `in` operator, `for...in`, `delete`, or any property added after the fact. Proxy operates at the object level: one handler covers every operation on the target, including dynamic keys and future additions. That gap is exactly why Vue 3 replaced the `Object.defineProperty`-based reactivity from Vue 2 with a Proxy-based system. The old approach missed array index assignments and new property additions. Proxy catches both. ### When to use - Log or audit every property read/write during development: `get` and `set` traps - Validate inputs on objects whose shape changes at runtime: `set` trap - Build a read-only view of an object: trap `set` and `deleteProperty` to throw - Grant temporary, revocable access to sensitive data: `Proxy.revocable()` with `setTimeout` on `revoke` - Implement reactive state like Vue 3 observables or MobX One observation from production: a validation Proxy that ended up inside a React render cycle caused visible frame drops. The profiler caught it immediately, but it was a clear reminder that Proxy overhead compounds fast when call count scales. ### How the JavaScript engine handles Proxy V8 stores a hidden handler slot on every Proxy instance. When you read `proxy.prop`, V8 checks whether the handler has a `get` trap. If yes, it calls your function with `(target, prop, receiver)`. If the trap is absent, V8 falls through to `Reflect.get(target, prop, receiver)` by default. The same logic applies to `set`, `deleteProperty`, `has`, `ownKeys`, `apply`, `construct`, and the other fundamental operations defined in the spec. Revocable proxies store a separate revoke function that nulls the handler slot when called. After revocation, any operation on the proxy throws a `TypeError` with no way to restore it. ### Common mistakes **1. Using the proxy reference inside its own trap** ```javascript // Wrong: p is the proxy, so p[prop] triggers get again const p = new Proxy({}, { get(target, prop) { return p[prop]; // infinite recursion } }); p.x; // RangeError: Maximum call stack size exceeded ``` ```javascript // Fix: use target (the raw object), not the proxy variable const p = new Proxy({}, { get(target, prop, receiver) { return Reflect.get(target, prop, receiver); } }); ``` **2. Skipping the receiver argument breaks method calls** ```javascript // Wrong: no receiver means `this` won't point to the proxy in inherited methods const p = new Proxy(obj, { get(target, prop) { return target[prop]; } }); ``` ```javascript // Fix: pass receiver so `this` stays correct through prototype chains const p = new Proxy(obj, { get(target, prop, receiver) { return Reflect.get(target, prop, receiver); } }); ``` **3. Mutating target directly inside a trap bypasses the set trap** ```javascript // Wrong: direct mutation on target skips set trap logic entirely const p = new Proxy({ count: 0 }, { get(target, prop) { target.count++; // no set trap fires here return target[prop]; } }); ``` If you need to write a value from inside a trap, use `Reflect.set(target, prop, value, receiver)` so the full trap chain runs. **4. Comparing proxy to original with ===** ```javascript const original = { x: 1 }; const proxy = new Proxy(original, {}); console.log(proxy === original); // false - they are different objects ``` Proxy creates a separate wrapper. Strict equality always fails. Track the original reference separately if identity comparison matters. **5. Forgetting to revoke a revocable proxy** ```javascript const secret = { apiKey: 'xyz-123' }; const { proxy, revoke } = Proxy.revocable(secret, {}); // Without revoke(), the proxy keeps secret reachable indefinitely setTimeout(revoke, 1000); console.log(proxy.apiKey); // xyz-123 (before revoke) // After revoke: TypeError: Cannot perform 'get' on a proxy that has been revoked ``` ### Real-world usage - **Vue 3**: dropped `Object.defineProperty` in favor of Proxy to handle array mutations and dynamic keys that Vue 2 missed - **MobX**: observable objects use Proxy traps to track reads and writes and trigger reactions - **Immer**: wraps a draft state object in a Proxy so you write mutations that produce immutable updates - **Zustand**: uses Proxy internally for devtools inspection of state changes - **Node.js vm2**: sandboxed code runs inside revocable Proxies that restrict access to host objects ### Follow-up questions **Q:** What is the full signature of the `get` trap? **A:** `get(target, property, receiver)`. `target` is the wrapped object, `property` is the key being accessed, `receiver` is the proxy itself (or the object that initiated the lookup through a prototype chain). **Q:** How does Proxy intercept `for...in` loops? **A:** Via the `ownKeys` trap. Return an empty array from it and `for...in` sees no properties. Without a trap, the loop behaves normally against the target. **Q:** What exactly happens after `revoke()` is called? **A:** The handler slot is set to null. Every subsequent operation on the proxy throws a `TypeError` immediately. There is no way to re-enable it. **Q:** Why is Proxy slower than direct property access? **A:** Each intercepted operation goes through an extra function call and internal handler lookup in V8. That overhead is roughly 2-5x compared to reading a plain property. It matters in hot code paths, not in typical application logic. **Q:** How would you implement a deep validation Proxy? (senior level) **A:** In the `set` trap, check whether the incoming value is an object or array. If it is, wrap it in another Proxy before assigning and recurse. That way every write at any depth goes through validation, not just the top-level assignment. ## Examples ### Basic: logging property access ```javascript const config = { debug: true, timeout: 3000 }; const trackedConfig = new Proxy(config, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); console.log(`[config] read "${prop}" = ${value}`); return value; }, set(target, prop, value, receiver) { console.log(`[config] set "${prop}" = ${value}`); return Reflect.set(target, prop, value, receiver); } }); trackedConfig.timeout; // [config] read "timeout" = 3000 trackedConfig.debug = false; // [config] set "debug" = false ``` Both traps delegate to `Reflect` with the full signature. The target stays untouched; all side effects live in the handler. ### Intermediate: runtime validation on a state object This follows the same pattern Zustand uses in its devtools middleware. ```javascript const state = { todos: [{ id: 1, text: 'Buy milk', done: false }] }; const validatedState = new Proxy(state, { set(target, prop, value, receiver) { if (prop === 'todos') { if (!Array.isArray(value)) throw new TypeError('todos must be an array'); value.forEach(todo => { if (typeof todo.text !== 'string') throw new TypeError('todo.text must be a string'); }); } return Reflect.set(target, prop, value, receiver); } }); validatedState.todos = [{ id: 2, text: 'Walk dog', done: false }]; // OK validatedState.todos = [{ id: 3, text: 123, done: false }]; // TypeError: todo.text must be a string ``` Validation runs on every assignment to `todos`. The original `state` object is never written to directly. ### Advanced: revocable access to sensitive data ```javascript function createTemporaryAccess(data, durationMs) { const { proxy, revoke } = Proxy.revocable(data, { get(target, prop, receiver) { console.log(`[access] reading ${String(prop)}`); return Reflect.get(target, prop, receiver); } }); setTimeout(revoke, durationMs); return proxy; } const credentials = createTemporaryAccess({ apiKey: 'xyz-123' }, 2000); console.log(credentials.apiKey); // [access] reading apiKey -> xyz-123 setTimeout(() => { try { console.log(credentials.apiKey); } catch (e) { console.log(e.message); // Cannot perform 'get' on a proxy that has been revoked } }, 3000); ``` After `revoke()` runs, the `credentials` reference still exists in memory, but every operation on it throws. The underlying `data` object becomes eligible for garbage collection if nothing else holds a reference to it.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.