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,constructand others. - A trap signature matches the
Reflectmethod of the same name one to one. - Inside a trap the default behaviour is invoked through
Reflect, not through direct access to thetarget. - If there is no trap for an operation, the operation runs on the
targetunchanged. - The
applyandconstructtraps only work when thetargetis a function.
Quick example
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"
// MariaHere 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
const proxy = new Proxy(target, handler);| Parameter | Description |
|---|---|
target | The original object, the real "target" |
handler | An object of traps, that is, methods that intercept actions |
The full list of the main traps
| Trap | What it intercepts | Counterpart |
|---|---|---|
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 check | in |
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 request | Object.getOwnPropertyDescriptor() |
defineProperty(target, prop, descriptor) | Defining a new property | Object.defineProperty() |
getPrototypeOf(target) | Getting the prototype | Object.getPrototypeOf() |
setPrototypeOf(target, proto) | Changing the prototype | Object.setPrototypeOf() |
isExtensible(target) | The extensibility check | Object.isExtensible() |
preventExtensions(target) | Forbidding new properties | Object.preventExtensions() |
apply(target, thisArg, args) | A function call | func() |
construct(target, args, newTarget) | A call with new | new func() |
Popular traps for objects
set, controlling writes:
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'; // TypeErrorhas, replacing the in operator:
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); // falseThis is how you can hide sensitive fields from existence checks.
ownKeys, hiding properties from enumeration:
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:
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
// 5construct, intercepting a call with new:
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.
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.
| Term | What it is |
|---|---|
| Trap | A handler method on a Proxy that intercepts an operation on the object |
| Handler | The object that holds the traps |
| Reflect | Used inside traps to invoke the default behaviour |
| Target | The real object the Proxy stands in front of |
Common mistakes
- Not returning a boolean from
set,deleteProperty,hasordefineProperty. These traps must returntrueorfalse;undefinedcounts asfalse, and in strict mode the operation fails with aTypeError. - Using
target[prop]instead ofReflect.get(target, prop, receiver). That loses thereceiver, so getters and inherited properties end up with the wrongthis. - Forgetting that
propcan be aSymbol. Interpolating a symbol key into a template string withoutString(prop)throws aTypeError. - Violating the invariants. A trap is not omnipotent:
ownKeysmust return every non-configurable own key, andgetcannot report a different value for a non-configurable, non-writable property. Otherwise the engine throws aTypeError. - Expecting
applyto work for an object. Theapplyandconstructtraps only work with functions. - Assuming traps intercept everything. Internal slots (for example in
Map,SetorDate) reach the real object past the traps, which is why such built-ins break behind aProxyunless their methods are bound.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.