JavaScript Proxy… But With Reflect

I began to wonder why Reflect was used to implement the Vue 3 Reactivity's Proxy trap. Upon Googling, only thing I could find was how to use the Proxy's handlers or other tutorials, so I decided to take a deeper look into it. This article reviews the basic concepts of Proxy and Reflect as well as explore why Reflect was used with the Proxy.

1. Metaprogramming

Before we dive headfirst into Proxy, allow me to introduce another topic. Proxy is a syntax that is built to support some feature within programming languages-the Metaprogramming.

1.1. When the Meta Language and The Target Language Are Different

The following example builds HTML from JSP. The meta language is Java, and the target language is HTML. (It’s meant to be a simple example, but because the target language is HTML, it’s a little bit weird. Just pretend you’re using <script> inside it.)

1.2. When The Meta Language and The Target Language Are Same

For the following example, I used an eval() to use JavaScript for both the meta language and the target language. eval analyzes the argument in runtime. Cases like this, where one programming language becomes the meta language of itself, is called reflection, and it manages and edits the structure and actions of itself in runtime.

1.3. Reflective Programming

Both Proxy and Reflect are implementations of reflection, and the reflection has three types.

  • Self-modification
    As the name suggests, it means that the program can change its own structure. Some examples are using the square brackets ([]) to get access to an attribute or using the delete operator to remove an attribute.
  • Intercession
    Intercession is an act of getting in the way on behalf of other, and programming wise, it means redefining some of the ways a language is executed. According to Axel Rauschmayer, the owner of 2ality, the ES2015’s Proxy was built to support this feature. (While we can't say that such support never existed because of methods like Object.defineProperty(), but if we focus on the fact that the intercession does not alter the target, he may be right.)

2. What Is A Proxy?

The Proxy object is used in place of the target object. Instead of using the target object directly, the Proxy object is used to transfer each process to the target object and then to return the results in code.

2.1. Creating the Proxy Object

  • handler : The handler (trap) object to be used for the intercession
  • [[Target]] : The target to be proxied; first argument
  • [[IsRevoked]] : Whether the object is revoked

2.2. Trap

The Proxy object mechanism acts through the trap function in order to redefine the target object’s basic instructions. All traps are optional, and if there are no traps, then the proxy object has no particular action.

2.3. Revocable Proxy Objects

Proxy object built using a constructor cannot be garbage collected nor reused. Therefore, a revocable Proxy can be built, if necessary.

3. What is Reflect?

Reflect is one of the built-in objects that provide methods that can intercept JavaScript commands like the Proxy.

3.1. Characteristics of Reflect

  • Reflect is a regular object, not a function object.
  • It has an internal slot of [[Prototype]], and its value is Object.prototype. (Reflect.__proto__ === Object.prototype)
  • It does not have a [[Construct]] slot, so it cannot be called with a new operator.
  • It does not have a [[Call]] slot, so it cannot be called as a function.
  • Every trap supported with Proxy is also supported for Reflect with a built-in method through the same interface.

3.2. Utility of Reflect

  • API Collected in a Single Namespace The Reflect namespace allows for more intuitive reflect APIs to be used compared to Object.
  • Cleaner Code You can implement error handling and reflection through Reflect with a cleaner code.

3.3. Reflect.get and Reflect.set

Now, let’s briefly go over Reflect.get and Reflect.set.

3.3.1. Reflect.get(target, propertyKey [, receiver])

Reflect.get returns target[propertyKey] by default. If the target is not an object, then it will throw a TypeError. This TypeError improves the ambiguity of JavaScript. For example, in the case of 'a'['prop'] will be evaluated as undefined, but Reflect.get will throw an actual error.

3.3.2. Reflect.set(target, propertyKey, V [, receiver])

Reflect.set works similarly to Reflect.get. However, as the name set suggests, it takes a V parameter to be assigned.

3.4. Interlude: receiver

In order to facilitate the understanding of the next topics, Reflect get/set and receiver, let's talk about the Receiver in the context of ECMAScript's object property lookup.

3.4.1. ECMAScript’s Property Lookup Process

First, let’s go through the process of ECMAScript’s property lookup. (As this part simply serves to facilitate your understanding, we will only discuss the flow of an Ordinary Object.)

  1. After reading, call GetValue(V) and then the [[Get]](P, Receiver) inner slot method.
  1. [[Get]](P, Receiver) is called, and then, OrdinaryGet(O, P, Receiver) is called. O is the child; P is job; and Receiver is child.
  2. Since child does not have the job, it recursively calls parent.[[Get]](P, Receiver) via prototype chaining. Here, the Receiver is passed on as is.
  3. Then, the OrdinaryGet(O, P, Receiver) is called, and O becomes the parent. Afterwards, it looks for job here, and then returns 'programmer'.

3.4.2. When Is The Receiver Used?

The Receiver is used only when the property found using OrdinaryGet(O, P, Receiver) is a getter, and is passed as the getter function's this value. Now, let's examine the following code.

  1. According to prototype chaining, the parent.[[Get]](P, Receiver) is called recursively and when the age, the getter is ran, the Receiver is used as this.

3.5. Reflect.get And Reflect.set's receiver

The Receiver, as explained earlier, is the object that receives the process request directly. The receiver of Reflect.get and Reflect.set works as the this context when the target[propertyKey] is getter or setter. In other words, it is through this receiver that you can manage the this binding.

4. Why Reflect Was Used With Proxy

Now, let’s finally discuss why Proxy and Reflect are used together. Evan You, the creator of Vue.js, mentions the Reflect in the Proxy's trap during an online lecture, saying that "while it's outside of the lecture's scope, the [Reflect] was used to deal with the prototype's side effects." Let's focus on what he means and see what happens when you use the reactive object, which is a Proxy object, as a prototype.

4.1. What If There Were No Reflect

The following code is taken from my previous Ins and Outs of Vue 3 Reactivity. I have taken the Proxy part out and changed it so that it didn't use Reflect. If we did not use Reflect and used a regular Proxy trap instead, then the program will throw errors because it does not know the target of the current search.

  1. When the parent's [[Get]] is called, the Proxy's get trap is triggered, and because the target inside the trap is the parent, when the program looks for target[key], it is identical to evaluating parent.age. Therefore, the this becomes the parent.
  1. When the parent's [[Set]] is called, the Proxy's set trap is triggered, and because the target[key] is parent['job'], the job property is added and is assigned to the parent.

4.2. Using the receiver Through Reflect

Now, let’s use Reflect in the Proxy's get/set traps and use the receiver to pass on the actual object that received the process request as this context to get rid of the side effects.

  1. In order to get the value, the program calls the Reflect.get, and the actual code becomes Reflect.get(parent, 'age', child).
  2. When the parent's get age() is called, this is bound to the child, and the instructions are carried out with respect to the child's age.
  1. In order to set the value, the program calls the Reflect.set, and the actual code becomes Reflect.set(parent, 'age', 'unemployed',child).
  2. The program passed the receiver and called Reflect.set, so the actual target of the process becomes the child.

JavaScript UI Library Open Source by