Reputation: 6807
Is there any significant difference between Reflect and Proxy?
From what is documented, it seems that they have pretty much the same capabilities, apart from:
If the list above sums up all the differences, then what is the rationale for having both?
Upvotes: 20
Views: 5609
Reputation: 92639
Reflect and Proxy have completely different purposes and different capabilities.
MDN describes Proxy in this way:
The
Proxy
object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers.
I realize that you've probably already read that, so I'll use an example to explain it further.
Let's say you have an object:
const obj = {
a: 'foo',
b: 'bar',
};
You can access property a
using a property accessor like this:
console.log(obj.a); // 'foo'
You can do the same using Reflect.get()
method:
console.log(Reflect.get(obj, 'a')); // 'foo'
You can also create a proxy of that object using the Proxy constructor. We'll use the get
handler for intercepting all property lookups.
const proxy = new Proxy(obj, {
get(target, property) {
return property in target ? target[property] : 'default';
},
});
Now using either property accessor or Reflect.get()
to get an undefined property results in string 'default'
:
console.log(proxy.c); // 'default'
console.log(Reflect.get(proxy, 'c')); // 'default'
Proxy and Reflect can work great together. You can for example create a Proxy with a no-op get
handler using Reflect:
new Proxy(obj, {
get: Reflect.get,
});
Upvotes: 26
Reputation: 1914
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 (*)
.
admin.name
, as admin
object doesn’t have such own property, the search goes to its prototype.userProxy
.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
Upvotes: 10