Skip to main content

Proxy traps

Traps are methods on a Proxy handler that intercept standard operations on an object (reading, writing and deleting properties, calling a function, the in operator and so on) and let you redefine their behaviour. Put simply, traps let you step into the work of JavaScript itself and change how an object behaves.

Theory

TL;DR

  • Traps are functions defined in the second argument of Proxy, the so-called handler.
  • Each trap corresponds to one internal operation: get, set, has, deleteProperty, ownKeys, apply, construct and others.
  • A trap signature matches the Reflect method of the same name one to one.
  • Inside a trap the default behaviour is invoked through Reflect, not through direct access to the target.
  • If there is no trap for an operation, the operation runs on the target unchanged.
  • The apply and construct traps only work when the target is a function.

Quick example

javascript
const user = { name: 'Maria' }; const proxy = new Proxy(user, { get(target, property) { console.log(`Reading property "${String(property)}"`); return target[property]; } }); console.log(proxy.name); // Reading property "name" // Maria

Here the get trap intercepts the property access, and before returning the value you can do anything: log it, check it, validate it or substitute a different value.

Syntax and the handler

javascript
const proxy = new Proxy(target, handler);
ParameterDescription
targetThe original object, the real "target"
handlerAn object of traps, that is, methods that intercept actions

The full list of the main traps

TrapWhat it interceptsCounterpart
get(target, prop, receiver)Reading a property (obj.prop)obj[prop]
set(target, prop, value, receiver)Writing a property (obj.prop = value)obj[prop] = value
has(target, prop)The prop in obj checkin
deleteProperty(target, prop)Deletion (delete obj[prop])delete
ownKeys(target)Getting the key list (Object.keys, for...in)Object.keys(obj)
getOwnPropertyDescriptor(target, prop)A property descriptor requestObject.getOwnPropertyDescriptor()
defineProperty(target, prop, descriptor)Defining a new propertyObject.defineProperty()
getPrototypeOf(target)Getting the prototypeObject.getPrototypeOf()
setPrototypeOf(target, proto)Changing the prototypeObject.setPrototypeOf()
isExtensible(target)The extensibility checkObject.isExtensible()
preventExtensions(target)Forbidding new propertiesObject.preventExtensions()
apply(target, thisArg, args)A function callfunc()
construct(target, args, newTarget)A call with newnew func()

set, controlling writes:

javascript
const user = { name: 'Maria' }; const proxy = new Proxy(user, { set(target, prop, value) { if (prop === 'age' && typeof value !== 'number') { throw new TypeError('Age must be a number'); } target[prop] = value; return true; // returning true is mandatory } }); proxy.age = 25; // ok proxy.age = 'twenty'; // TypeError

has, replacing the in operator:

javascript
const secret = { password: '1234' }; const proxy = new Proxy(secret, { has(target, key) { if (key === 'password') return false; return key in target; } }); console.log('password' in proxy); // false

This is how you can hide sensitive fields from existence checks.

ownKeys, hiding properties from enumeration:

javascript
const user = { name: 'Maria', password: '12345' }; const proxy = new Proxy(user, { ownKeys(target) { return Object.keys(target).filter(k => k !== 'password'); } }); console.log(Object.keys(proxy)); // ["name"]

Traps for functions: apply and construct

apply, intercepting a function call:

javascript
function sum(a, b) { return a + b; } const proxy = new Proxy(sum, { apply(target, thisArg, args) { console.log(`Called with arguments: ${args}`); return Reflect.apply(target, thisArg, args); } }); console.log(proxy(2, 3)); // Called with arguments: 2,3 // 5

construct, intercepting a call with new:

javascript
function User(name) { this.name = name; } const proxy = new Proxy(User, { construct(target, args) { console.log('Creating a new user...'); return new target(...args); } }); const maria = new proxy('Maria'); // "Creating a new user..."

Both traps only fire when the target is a function: you cannot wrap a plain object and intercept apply on it.

Delegating the default behaviour through Reflect

To avoid breaking native behaviour, traps usually call Reflect inside: it performs the "real" operation under the hood.

javascript
const proxy = new Proxy(user, { get(target, prop, receiver) { console.log(`Reading ${String(prop)}`); return Reflect.get(target, prop, receiver); } });

This guarantees that everything you did not intend to change stays at its default. The third receiver argument is not decorative: it passes the correct this to getters, whereas a plain target[prop] loses that context.

Where traps are actually used

  • Validation and data protection.
  • Logging property access.
  • Creating "virtual" properties that do not physically exist on the object.
  • Reactive systems (Vue, MobX).
  • ORMs and API wrappers, where models are substituted dynamically.
  • Metaprogramming and test mocks.
TermWhat it is
TrapA handler method on a Proxy that intercepts an operation on the object
HandlerThe object that holds the traps
ReflectUsed inside traps to invoke the default behaviour
TargetThe real object the Proxy stands in front of

Common mistakes

  • Not returning a boolean from set, deleteProperty, has or defineProperty. These traps must return true or false; undefined counts as false, and in strict mode the operation fails with a TypeError.
  • Using target[prop] instead of Reflect.get(target, prop, receiver). That loses the receiver, so getters and inherited properties end up with the wrong this.
  • Forgetting that prop can be a Symbol. Interpolating a symbol key into a template string without String(prop) throws a TypeError.
  • Violating the invariants. A trap is not omnipotent: ownKeys must return every non-configurable own key, and get cannot report a different value for a non-configurable, non-writable property. Otherwise the engine throws a TypeError.
  • Expecting apply to work for an object. The apply and construct traps only work with functions.
  • Assuming traps intercept everything. Internal slots (for example in Map, Set or Date) reach the real object past the traps, which is why such built-ins break behind a Proxy unless their methods are bound.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.