Proxy is a wrapper around an object, that forwards operations on it to the object, optionally trapping some of them. The Proxy object allows you to create an object that can be used in place of the original object, but which may redefine fundamental Object operations like getting, setting, and defining properties.

Reflect API is designed to complement Proxy. Internal methods such as [[Get]], [[Set]] and others are specification-only, can’t be called directly. The Reflect object makes that somewhat possible.
So Proxy is a wrapper which can be used to intercept fundamental operations like [[Get]] and [[Set]] on an object whereas Reflect provides us minimal wrappers around these fundamental operations like [[Get]] and [[Set]] so that we can call them directly (Usually from inside the trap).
------------ How Reflect complements Proxy ------------
For every internal method, trappable by Proxy, there’s a corresponding method in Reflect, with the same name and arguments as the Proxy trap. (!Important)
Let's see this example to demonstrate how it is useful.
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
// return target[prop];
}
});
alert(userProxy.name); // Guest
In the above example, inside the get trap, both return Reflect.get(target, prop, receiver); and return target[prop]; will print the same output (Guest).
Lets take an example that is little bit more complex to demonstrate why Reflect.get is better and why get/set have the third argument receiver.
Lets create an object admin which inherits from user:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop]; // (*) target = user
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
// Expected: Admin
alert(admin.name); // outputs: Guest (?!?)
Reading admin.name should return "Admin", not "Guest"!
The problem is actually in the proxy, in the line (*).
- When we read
admin.name, as admin object doesn’t have such own property, the search goes to its prototype.
- The prototype is
userProxy.
- When reading
name property from the proxy, its get trap triggers and returns it from the original object as target[prop] in the line (*). A call to target[prop], when prop is a getter, runs its code in the context this=target. So the result is this._name from the original object target, that is: from user.
To fix this we need to pass correct this to the getter. receiver, the third argument of get trap keeps the correct this to be passed to a getter (in our case that’s admin). For a regular function we can use call/apply to bind this value, but we cannot do the same for getter as it is not called, just accessed.
This is where Reflect is usefull. Remember, for every internal method, trappable by Proxy, there’s a corresponding method in Reflect, with the same name and arguments as the Proxy trap.
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver); // (*)
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
alert(admin.name); // Admin
For detailed explanation please refer to this wonderful article by Ilya Kantor : Proxy and Reflect