JavaScript simple meal -- Proxy and reflection

Keywords: Javascript Front-end ECMAScript

preface

The purpose of writing this series of articles on JavaScript simple meal is to record various knowledge points when reading and learning the book JavaScript advanced programming (4th Edition). Although it is a note and summary of reading, I hope it is light, concise and sharp, will not cause reading fatigue, and can take a light bath of knowledge points in fragmented time and leisure. Each article is only for a small part to explain and sort out, so as to achieve the purpose of personal review, summary and sharing knowledge.

ECMAScript6 adds proxy and reflection, so that we can define an associated proxy object for the target object, which can be used as an abstract target object. Before various operations on the target object affect the target object, these operations can be controlled in the proxy object.

1, Proxy Foundation

A Proxy is an abstraction of the target object. In many ways, a Proxy is like a pointer in C + + because it can be used as a substitute for the target object, but it is completely independent of the target object. The target object can be operated directly or through a Proxy. To say a word: an agent is an object's Avatar, and all operations on the avatar will affect the "ontology". The simplest way to create a Proxy is to create an empty Proxy, that is, do nothing but abstract as an object. The Proxy is created using the Proxy constructor. This constructor takes two parameters: the target object and the handler object, both of which are indispensable. If you don't say much, go straight to the code:
const person = {
    name: 'Lucy'
};

const handler = {};

const proxy = new Proxy(person, handler); // (target object, handler object)

console.log(person.name); // Lucy
console.log(proxy.name); // Lucy

person.name = 'Jack'; // If you change the name attribute on the person object (target object), it will also change on the proxy
console.log(person.name); // Jack
console.log(proxy.name); // Jack

console.log(person === proxy) // false

2, Define catcher

The main purpose of using agents is to define traps. The catcher is the "interceptor of basic operations" defined in the handler object. Each handler object can contain zero or more traps, each corresponding to a basic operation, which can be called directly or indirectly on the proxy object. Each time these basic operations are called on the proxy object, the proxy can call the catcher function before these operations are propagated to the target object, so as to intercept and modify the corresponding behavior. Or directly on the code to intuitive understanding. Here we define a get() catcher in the handler object in the example just now.
const person = {
    name: 'Lucy'
};

const handler = {
    get() {
        return 'Trap triggered!'
    }
};

const proxy = new Proxy(person, handler); // (target object, handler object)
In this way, when the get() operation is performed through the proxy object, the defined get() catcher will be triggered. All of these actions trigger the get() catcher whenever they occur on the proxy object. Note that the catcher is triggered only when these operations are performed on the proxy object. Performing these operations on the target object will still produce normal behavior. Let's do an experiment:
console.log(person.name); // Lucy
console.log(proxy.name); // Trap triggered!
Sure enough, the catcher fires when a get operation is performed on the proxy object!

3, Catcher parameters and reflection API s

All captors can access the corresponding parameters, based on which the original behavior of the captured method can be reconstructed. For example, the get() catcher will receive three parameters: the target object, the property to query, and the proxy object:
const person = {
    name: 'Lucy'
};

const handler = {
    get(trapTarget, property, receiver) {
        console.log(trapTarget === person);
        console.log(property);
        console.log(receiver === proxy);
    }
};

const proxy = new Proxy(person, handler); // (target object, handler object)

proxy.name;
// true
// name
// true
With these parameters, you can reconstruct the original behavior of the captured method:
const person = {
    name: 'Lucy'
};

const handler = {
    get(trapTarget, property, receiver) {
        return trapTarget[property];
    }
};

const proxy = new Proxy(person, handler); // (target object, handler object)

console.log(person.name); // Lucy
console.log(proxy.name); // Lucy
Although we can manually reconstruct the original behavior, it is completely unnecessary because there are many operations more complex than get. For this purpose, we can easily rebuild by calling the method with the same name on the global Reflect object. All methods that can be captured in the handler object have corresponding reflection API methods. These methods have the same name and function signature as the method intercepted by the catcher, and also have the same behavior as the intercepted method. Therefore, using the reflection API, you can also define an empty proxy object as follows:
const person = {
    name: 'Lucy'
};

const handler = {
    get() {
        return Reflect.get(...arguments);
    }
};

const proxy = new Proxy(person, handler); // (target object, handler object)

console.log(person.name); // Lucy
console.log(proxy.name); // Lucy
If you really want to create an empty proxy that can capture all methods and forward each method to the corresponding reflection API, you don't even need to define a handler object:
const person = {
    name: 'Lucy'
};

const proxy = new Proxy(person, Reflect); // (target object, handler object)

console.log(person.name); // Lucy
console.log(proxy.name); // Lucy

4, Catcher invariant

Using traps can change the behavior of almost all basic methods, but it is not unlimited. According to ECMAScript specification, each captured method knows the context of the target object and the signature of the capture function, and the behavior of the capture handler must comply with the "catcher invariant". This rule usually prevents too abnormal behavior in the definition of the catcher. For example, if the target object has a non configurable and non writable property, TypeError will be thrown when the catcher returns a value different from the property. The code is as follows:
const person = {};
Object.defineProperty(person, 'name', {
    configurable: false,
    writable: false,
    value: 'Lucy'
});

const handler = {
    get() {
        return 'Jack';
    }
};

const proxy = new Proxy(person, handler);

console.log(proxy.name); // TypeError: 'get' on proxy: property 'name' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'Lucy' but got 'Jack')

5, Summary

The above is what I want to talk about today. Today, I briefly introduce the proxy foundation, the creation method of proxy, the definition of catcher, the role and usage of catcher, reflection API and catcher invariants. In the next article, let's continue to delve into proxy and reflection. Sprinkle flowers~

Posted by Jay_Seagrave on Sat, 06 Nov 2021 16:39:30 -0700