skip to content
usubeni fantasy logo Usubeni Fantasy

Reflect and Proxy in JavaScript

/ 5 min read

This Post is Available In: CN EN

Reflect

There is a song called “Reflection” in Disney’s version of Mulan. Mulan looks at her reflection in the water, contemplating her future choices. The lyrics and visuals of the song contain both meanings of the word “reflection”:

  • Reflection as in “to reflect” or “to mirror”
  • Reflection as in “to contemplate” or “to introspect”

Translating Reflect as “反射” (reflection) in programming can indeed be confusing. In fact, it should lean towards the direction of “self-reflection” or “introspection”. Reflect is one of the important methods in JavaScript metaprogramming. Metaprogramming allows you to achieve the desired functionality from a higher dimension, such as adjusting the original behavior of a program, invoking internal methods, or writing code programmatically. Of course, Reflect is not an exclusive term in JavaScript; other languages like Go and Java also have Reflect methods.

JavaScript’s Reflect has the following methods:

Seeing this bunch of stuff, I can’t help but exclaim, familiar, too familiar. It seems like I’ve used many of these methods somewhere before. Indeed, Reflect intentionally moved some methods that were hanging on Object and Function over here.

// For example, the familiar apply
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
// Can be written as
Reflect.apply(Math.floor, undefined, [1.75]);

This brings the first obvious benefit of using Reflect, simplified code. In addition, apply actually calls the internal method [[Call]] of the JavaScript engine, so Reflect falls into the category of metaprogramming.

For example, Reflect.ownKeys takes on the functionality of Object.getOwnPropertyNames to inspect the properties of an object:

const object1 = {
a: 1,
b: 2,
c: 3,
};
console.log(Reflect.ownKeys(object1));
console.log(Object.getOwnPropertyNames(object1));
// Expected output: Array ["a", "b", "c"]

Methods like Reflect.deleteProperty() and Reflect.has() take on the functionality of some operators:

const obj = { x: 1, y: 2 };
Reflect.deleteProperty(obj, "x"); // true
console.log(obj); // { y: 2 }
// deleteProperty and delete have the same functionality, but deleteProperty returns the operation result, while delete operates silently
// Although I still want to complain, does delete operation really fail...
const obj = { x: 1, y: 2 };
delete obj.x;
console.log(obj); // { y: 2 }

In methods like get and set, as before, the internal methods [[Get]] and [[Set]] are called:

const object1 = {
x: 1,
y: 2,
};
console.log(Reflect.get(object1, "x"));

But this becomes confusing. object1.x also calls the internal method [[Get]], and it’s even simpler. Why use the cumbersome method Reflect.get? The answer is to work with Proxy.

Proxy

Proxy is easy to understand, and I believe everyone has often seen this word in certain fields (dog head). Here, it also means that it is a proxy. A Proxy instance is an object, and your operations on this object will be proxied by other functions.

The most basic Proxy looks like this:

const target = {
message1: "hello",
message2: "everyone",
};
const handler = {};
const proxy1 = new Proxy(target, handler);

In fact, nothing will happen with this Proxy, everything will remain the same. If you want something to happen, you can work with the handler.

function proxyFactory(traps) {
let target = {};
let handler = {};
for (let i = 0; i < traps.length; i++) {
let trap = traps[i];
handler[trap] = function (...args) {
console.log("trap: " + trap, args);
return Reflect[trap](...args);
};
}
let test = new Proxy(target, handler);
return test;
}

You can see that proxyFactory needs to pass in a traps list. When the function runs, it puts the trap into the handler, and then new Proxy(target, handler) is used to operate the object using the trap as a proxy.

So what is a trap? It can be considered as a “trap” that allows certain operations to “fall into” this “trap” and directly execute the trap function.

According to the definition on MDN, a trap is a function that defines the behavior for the corresponding object internal method. This concept is analogous to traps in operating systems.

Based on this definition, traps perfectly align with the functionality of Reflect. In fact, the method names of Reflect are exactly the same as the names of the traps. Therefore, we can set it up like this:

let test = proxyFactory(Reflect.ownKeys(Reflect));

With this setup, the test object will log when you make any internal method calls. For example:

test.a = 1;
// trap: set (4) [{…}, 'a', 1, Proxy]
// trap: getOwnPropertyDescriptor (2) [{…}, 'a']
// trap: defineProperty (3) [{…}, 'a', {…}]
test.a;
// trap: get (3) [{…}, 'a', Proxy]
"a" in test;
// trap: has (2) [{…}, 'a']

In this example, we are just printing out the operations and parameters. In reality, you can perform any desired operations on the object within the trap. This opens up a wide range of programming possibilities. For example, Vue 3’s reactivity principle is closely tied to Reflect and Proxy.

Takeaway

  • Reflect is an important object for JavaScript metaprogramming. It allows you to call internal methods of objects and perform internal operations on them.
  • Reflect makes functions like Function.prototype.apply.call and Object.getOwnPropertyNames more intuitive and concise.
  • Reflect takes on the functionality of operators like in and returns the operation results.
  • Reflect can be perfectly combined with Proxy to define object behavior arbitrarily.

Reference

评论组件加载中……