Why is Reflect needed together with Proxy?
The problem before Reflect existed
Before ES6, there was no way at all to intercept operations on an object.
With the introduction of Proxy this became possible, but it immediately raised another problem:
How do you invoke the "default behavior" after intercepting an operation?
For example, you want to log property access, but not break the object's behavior:
const user = { name: "Tim" };
const proxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(`Reading property "${prop}"`);
return target[prop]; // works, but not always correctly
}
});This is fine so far, but if:
- the property is on the prototype,
- or it's a getter,
- or inheritance is involved,
then plain target[prop] can behave differently from regular obj.prop.
The solution: Reflect
To keep Proxy traps from breaking JS's normal behavior, Reflect was introduced.
Reflect provides functional counterparts to all the language's basic operations that Proxy can intercept.
For example:
const proxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(`Reading "${prop}"`);
// a safe call to the default behavior:
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Writing "${prop}" = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
});Now the proxy behaves exactly like the original object, just with extra logic added.
Why you can't just use target[prop]
Here's an example where the behavior breaks without Reflect
const proto = {
get name() {
console.log("Getter from the prototype");
return "Alex";
}
};
const obj = Object.create(proto);
const proxy = new Proxy(obj, {
get(target, prop) {
console.log("Intercepting get");
return target[prop]; // the getter runs with the wrong this
}
});
console.log(proxy.name);Result:
Intercepting get
Getter from the prototypebut inside the getter, this will refer to target, not proxy.
The fix, using Reflect.get:
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
console.log("Intercepting get");
return Reflect.get(target, prop, receiver); // correct
}
});
console.log(proxy.name);
// Intercepting get
// Getter from the prototypeNow this inside the getter correctly points to proxy.
A one-to-one relationship
Every Proxy trap has a matching Reflect method that performs the default behavior:
| Proxy trap | Reflect method | What it does |
|---|---|---|
get | Reflect.get() | Reads a property |
set | Reflect.set() | Sets a value |
has | Reflect.has() | Checks prop in obj |
deleteProperty | Reflect.deleteProperty() | Deletes a property |
ownKeys | Reflect.ownKeys() | Gets all keys |
getOwnPropertyDescriptor | Reflect.getOwnPropertyDescriptor() | Gets a descriptor |
defineProperty | Reflect.defineProperty() | Defines a property |
getPrototypeOf | Reflect.getPrototypeOf() | Gets the prototype |
setPrototypeOf | Reflect.setPrototypeOf() | Changes the prototype |
isExtensible | Reflect.isExtensible() | Checks extensibility |
preventExtensions | Reflect.preventExtensions() | Prevents extension |
apply | Reflect.apply() | Calls a function |
construct | Reflect.construct() | Calls via new |
So in every Proxy trap you can simply add your own logic and delegate the default operation to Reflect.
Example: logging every operation without breaking the logic
const user = { name: "Tim" };
const proxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`SET ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
deleteProperty(target, prop) {
console.log(`DELETE ${prop}`);
return Reflect.deleteProperty(target, prop);
}
});
proxy.name; // GET name
proxy.age = 30; // SET age = 30
delete proxy.age;// DELETE ageWithout Reflect you'd have to manually emulate the language's behavior (which is difficult and unsafe).
Additional advantages of Reflect
- Its methods return true/false instead of throwing errors. Convenient for checking whether an operation succeeded.
Reflect.defineProperty({}, 'x', { value: 1 }); // true- You can programmatically perform operations that are normally expressed with operators (
delete,in,new, calling a function).
Reflect.apply(Math.max, null, [1, 2, 3]); // 3
Reflect.construct(Date, [2025, 0, 1]); // equivalent to new Date(2025, 0, 1)- You build a
Proxy, safely delegate throughReflect, and never break the language.
Summary
| What | Role |
|---|---|
Proxy | Intercepts operations on an object |
Reflect | Lets you perform the "original" operation inside a trap |
| Why together | To extend or modify an object's behavior without breaking standard JS semantics |
| Advantage | Predictable code that's compatible with inheritance, getters/setters, and built-in objects |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.