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
Reflect as well as explore why
Reflect was used with the
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.
Metaprogramming is when one computer program treats another program as data. In other words, a program is designed to read another program to either analyze or transform it. The language used for the metaprogramming is called meta language, and the target language is the language that is the target of manipulation. Meta language and target language may or may not be same.
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 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.
Now, let’s discuss reflection, a branch of metaprogramming.
1.3. Reflective Programming
Reflect are implementations of reflection, and the reflection has three types.
- Type Introspection
This is when the program accesses its structure in runtime to learn a type or an attribute.
Object.keys()is an example of such case.
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
deleteoperator to remove an attribute.
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
Proxywas 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
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
The Proxy object must be created using the
Proxy object takes two following parameters.
target: The target object of the intercession
handler: The handler (trap) object to be used for the intercession
The code above is just an empty Proxy object. The variable
p has the following structure.
[[Handler]]: Maps the second argument
[[Target]]: The target to be proxied; first argument
[[IsRevoked]]: Whether the object is revoked
The above is the most basic Proxy construction, and you must be aware that once you set the Proxy’s target object, it cannot be changed.
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.
Once a Proxy is created, the trap cannot be added or deleted, and if you need to change something about it, you must create a new Proxy. For more information regarding traps, refer to the MDN document.
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.
revocable() method returns a new object that contains the Proxy object with the
revocable.proxy object is identical to the Proxy object built using a constructor. Once the
revoke() method is called, the Proxy object's
[[IsRevoked]] value is set to
true. If the revoked Proxy objects are triggered again, the
TypeError is thrown and the objects are garbage collected.
3. What is
3.1. Characteristics of
Reflectis a regular object, not a function object.
- It has an internal slot of
[[Prototype]], and its value is
Reflect.__proto__ === Object.prototype)
- It does not have a
[[Construct]]slot, so it cannot be called with a
- It does not have a
[[Call]]slot, so it cannot be called as a function.
- Every trap supported with
Proxyis also supported for
Reflectwith a built-in method through the same interface.
3.2. Utility of
- API Collected in a Single Namespace The
Reflectnamespace allows for more intuitive reflect APIs to be used compared to
- Cleaner Code You can implement error handling and reflection through
Reflectwith a cleaner code.
- More Stable Call If you have worked with a strict ESLint Rule Preset, you may have came across no-prototype-builtins rule. This rule exists to prevent bugs that can occur when the method name of a default built in object is the name of the attribute of an instance method. It prevents developers from using methods from
Object.prototypein instances. This means that you can't use
obj.hasOwnPropertyand that you have to write out
Object.prototype.hasOwnProperty.call, but you can use
Reflectto write concisely and follow the rule at the same time.
Now, let’s briefly go over
Reflect.get(target, propertyKey [, receiver])
target[propertyKey] by default. If the
target is not an object, then it will throw a
'a'['prop'] will be evaluated as
Reflect.get will throw an actual error.
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.
In order to facilitate the understanding of the next topics,
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.)
ECMAScript specs examine the objects’ properties in the following ways.
- Read as
- After reading, call
GetValue(V)and then the
[[Get]](P, Receiver)inner slot method.
Now let’s look at the following code.
The reading process is as follows:
GetValue(V)is called, the
jobproperty we are looking for, and the
Receiver, the resulting value of
this's context value,
[[Get]](P, Receiver)is called, and then,
OrdinaryGet(O, P, Receiver)is called.
childdoes not have the
job, it recursively calls
parent.[[Get]](P, Receiver)via prototype chaining. Here, the
Receiveris passed on as is.
- Then, the
OrdinaryGet(O, P, Receiver)is called, and
parent. Afterwards, it looks for
jobhere, and then returns
For more details regarding the spec, refer to this link, but the important thing is that the
Receiver remains intact even after object lookup via prototype chaining.
3.4.2. When Is The
Receiver is used only when the property found using
OrdinaryGet(O, P, Receiver) is a
getter, and is passed as the
this value. Now, let's examine the following code.
The code above works as follows.
child.[[Get]](P, Receiver)is called, and the
- According to prototype chaining, the
parent.[[Get]](P, Receiver)is called recursively and when the
getteris ran, the
Receiveris used as
Receiver reveals information regarding the object that received the initial process request in the prototype chaining. While the example only shows it working with
[[Set]] follows a similar process(Reference) to set the
child as the
Receiver, as explained earlier, is the object that receives the process request directly. The
Reflect.set works as the
this context when the
setter. In other words, it is through this
receiver that you can manage the
The following example applies the
receiver in a different way in order to modify the
this binding and to return a sum, differently.
The following example uses the
Receiver in cases of
setter, and you can use the
receiver parameter with the
set traps to control them.
Reflect Was Used With
Now, let’s finally discuss why
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
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.
The following code is based on the code above. The example modifies a
child that has a
reactivityParent, a Proxy object, as its prototype.
- Once the program searches for the
child, the search continues through the Proxy object via prototype chaining. (Reference - step 3)
- When the
[[Get]]is called, the
gettrap is triggered, and because the
targetinside the trap is the
parent, when the program looks for
target[key], it is identical to evaluating
parent.age. Therefore, the
- Once you assign the
jobproperty to be
'unemployed', the search continues through the Proxy object via prototype chaining. (Reference - step 2)
- When the
[[Set]]is called, the
settrap is triggered, and because the
jobproperty is added and is assigned to the
child's age is set to
40, and the job is assigned in the
reactivityParent. Something's not right.
4.2. Using the
Now, let’s use
Reflect in the
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.
- As in code 4.1., the
gettrap is triggered.
- In order to get the value, the program calls the
Reflect.get, and the actual code becomes
Reflect.get(parent, 'age', child).
- When the
get age()is called,
thisis bound to the
child, and the instructions are carried out with respect to the
- As in code 4.1., the
settrap is triggered.
- In order to set the value, the program calls the
Reflect.set, and the actual code becomes
Reflect.set(parent, 'age', 'unemployed',child).
- The program passed the
Reflect.set, so the actual target of the process becomes the
child's age is displayed accurately, and the job is assigned to be
What started out to discuss how
Reflect is related
Proxy, became more about understanding the