halfzebra
halfzebra

Reputation: 6807

What is the difference between Proxy constructor and Reflect?

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

Answers (2)

Michał Perłakowski
Michał Perłakowski

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).

And Reflect in this way:

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

PR7
PR7

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.

Proxy

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 (*).

  1. When we read admin.name, as admin object doesn’t have such own property, the search goes to its prototype.
  2. The prototype is userProxy.
  3. 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

Upvotes: 10

Related Questions