← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

JavaScript Proxy Object

javascript ES2015 Advanced
debt(d8/e6/b6/t8)
d8 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'silent in production until users hit it' (d8). detection_hints.automated is no, and the regex only catches the literal new Proxy pattern. Internal slot bypass, missing Reflect delegation, and identity bugs typically only surface at runtime when specific code paths hit them, with no standard linter rule catching these.

e6 Effort Remediation debt — work required to fix once spotted

Closest to 'cross-cutting refactor across the codebase' (e6). quick_fix says to delegate every trap to Reflect and avoid wrapping internal-slot objects — but if a Proxy is already woven into reactivity/observability layers, fixing it often requires touching every trap and every call site that compares identity or stores keys, which is more than a single component.

b6 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (b6). applies_to spans web/node/library and tags include reactivity — a Proxy at the heart of a reactivity or validation system shapes how all consumers interact with data (identity, equality, method binding), but it's not necessarily the whole system's shape, hence slightly below b7.

t8 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t8). The misconception is explicit: developers reasonably assume traps fire for every operation, but internal slot access bypasses them entirely, causing TypeErrors on Map/Set/Date/#private. This directly contradicts the intuitive mental model of 'proxy intercepts everything', pushing it toward catastrophic.

About DEBT scoring →

Also Known As

Proxy ES6 Proxy proxy handler meta-programming JS

TL;DR

Proxy wraps an object to intercept fundamental operations like property access, assignment, and function calls via trap handlers.

Explanation

The Proxy constructor (ES2015) creates a transparent wrapper around a target object. You define a handler object whose methods (called traps) intercept operations like get, set, has, deleteProperty, apply, construct, ownKeys, and getOwnPropertyDescriptor. Reading or writing through the proxy invokes the matching trap; omitted traps fall through to default behaviour on the target.

Common use cases include validation (reject writes that violate a schema), reactive systems (notify on property changes - the foundation of Vue 3 reactivity), negative array indices, default values for missing properties, logging and debugging, virtual properties that compute on access, access control, and immutable views. Reflect provides a companion API whose methods mirror trap signatures, so handlers usually delegate to Reflect.get(target, prop, receiver) etc. to preserve correct semantics around inheritance and receiver binding.

Proxies have caveats. They are not transparent for identity-sensitive code: proxy !== target. Some built-in operations bypass traps when they access internal slots directly - wrapping a Map or Date and then calling its methods on the proxy will usually throw because the methods need the original internal slot. Private class fields also bypass proxies for the same reason. Performance is meaningfully worse than direct property access; do not wrap hot data structures without measuring. Proxies cannot be revoked unless created via Proxy.revocable, and once revoked any access throws TypeError.

Unlike Object.defineProperty (which intercepts known properties one at a time), Proxy intercepts all properties including ones added after creation, which is why modern reactive frameworks moved to it. Compared to PHP magic methods like __get and __set, Proxy traps are more uniform and complete - they cover deletion, enumeration, and prototype operations as well.

Common Misconception

Proxy traps fire for every operation on the target. Many built-in operations access internal slots directly and bypass proxies entirely - wrapping a Map, Set, Date, or class with private fields and calling its methods through the proxy will throw because the method runs with the proxy as `this` but needs the original internal slot.

Why It Matters

Proxy enables generic interception patterns - validation, reactivity, observability - that previously required per-property accessors or build-time codegen, but its subtle semantics around `this`, internal slots, and identity cause hard-to-debug failures when used carelessly.

Common Mistakes

  • Forgetting to delegate to Reflect inside traps, breaking receiver semantics and inherited getters/setters.
  • Wrapping Map, Set, Date, or other objects with internal slots and then calling their methods through the proxy (TypeError on internal slot access).
  • Comparing proxy to target with === and being surprised they differ; storing the target as a map key while looking up the proxy.
  • Adding heavy logic in get/set traps on hot paths, causing severe performance regressions.
  • Assuming private class fields (#field) are observable through a proxy - they bypass traps entirely.

Code Examples

✗ Vulnerable
// Naive trap - breaks inherited getters and `this` binding
const user = new Proxy(target, {
  get(obj, prop) {
    return obj[prop]; // ignores receiver, no Reflect
  },
  set(obj, prop, value) {
    obj[prop] = value;
    return true;
  }
});

// Wrapping a Map - looks fine, throws at runtime
const m = new Proxy(new Map(), {});
m.set('a', 1); // TypeError: Method Map.prototype.set called on incompatible receiver
✓ Fixed
// Delegate through Reflect, validate on set
const schema = { name: 'string', age: 'number' };

function validated(target) {
  return new Proxy(target, {
    get(obj, prop, receiver) {
      return Reflect.get(obj, prop, receiver);
    },
    set(obj, prop, value, receiver) {
      if (prop in schema && typeof value !== schema[prop]) {
        throw new TypeError(`${prop} must be ${schema[prop]}`);
      }
      return Reflect.set(obj, prop, value, receiver);
    }
  });
}

// Revocable proxy for time-limited access
const { proxy, revoke } = Proxy.revocable({ secret: 42 }, {});
console.log(proxy.secret); // 42
revoke();
// proxy.secret -> TypeError

// For Map/Set, rebind methods so internal slot access works
const raw = new Map();
const wrapped = new Proxy(raw, {
  get(obj, prop, receiver) {
    const v = Reflect.get(obj, prop, receiver);
    return typeof v === 'function' ? v.bind(obj) : v;
  }
});

Added 16 May 2026
Views 30
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W 2 pings T 1 ping F 0 pings S 1 ping S 0 pings M 1 ping T 1 ping W 1 ping T 0 pings F 2 pings S 0 pings S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Google 4 ChatGPT 4 Perplexity 4 Meta AI 2 SEMrush 2 Ahrefs 1
crawler 16 crawler_json 1
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Always delegate from traps to Reflect.<trap>(target, ...args) to preserve correct semantics, and avoid wrapping objects with internal slots (Map, Set, Date) unless you rebind their methods.
📦 Applies To
javascript ES2015 web node library vue
🔗 Prerequisites
🔍 Detection Hints
new Proxy\(.+,\s*\{[^}]*(get|set)\s*\([^)]*\)\s*\{[^}]*\breturn\s+\w+\[
Auto-detectable: ✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: File Tests: Update

✓ schema.org compliant