Chapter 9 Agency and Reflection
Three main points
1. Agency basis
2. Code catcher and reflection method
3. Proxy mode
9.1 Agent Basics
A proxy is an abstraction of a target object, similar to a pointer in C++ because it can be used as a proxy for the target object, but completely independent of the target object.
9.1.1 Creating an Empty Proxy
The simplest proxy is an empty proxy, which abstracts a target object and does nothing. By default, primitive operations performed on a proxy object are propagated to the target object in five drops. Therefore, wherever the target object can be used, the proxy object associated with it can be used in the same way.
The Proxy is created through the Proxy constructor. This constructor takes two arguments: the target object and the handler object, and the absence of either raises TypeError
Sample 1 changes the object and the pointer changes
const target={ id:'thisISID' }; const handler={}; const proxy=new Proxy(target,handler); // The id attribute accesses the same value console.log(target.id); // return 'thisISID' console.log(proxy.id); // Return 'thisISID' // Change the target object's properties, the pointer will also change target.id=" I'm going to change "; console.log(target.id); // Return 'I want to change' console.log(proxy.id); // return 'I want to change'Copy the code
hasOwnProperty()
console.log(target.hasOwnProperty('id')); // Return true conosle.log(proxy.hasownProperty ('id')); / / return trueCopy the code
Can’t use the instanceof
console.log(target instanceof Proxy); //TypeError
console.log(proxy instacneof Proxy); //TypeError
Copy the code
9.1.2 Defining a trap
The purpose of using proxies is to define traps, which are “interceptors for basic operations” defined in handler objects.
sample
const target={ foo:"bar" }; Const Handler ={// The catcher uses the normal key get(){return 'not allowed to read' in the handler object; }}; const proxy=new Proxy(target,handler); console.log(proxy.foo); // Console. log(target.foo) is blocked by get. // Returns bar unaffected on the target objectCopy the code
9.1.3 Trap parameters and reflection APIS
All catchers have access to parameters based on which the original behavior of the capturing method can be reconstructed. The GET () trap takes the target object, the properties of the query, and the proxy object
Const target={name:"JackMa", age:59, job:" JackMa"}; const handler={ get(trapTarget,property,receiver){ console.log(trapTarget===target); console.log(property); console.log(receiver===proxy); }}; const proxy=new Proxy(target,handler); proxy.name; // Return true name trueCopy the code
The original behavior of the captured method can be reconstructed using the above traps
Const target={name:"JackMa", age:59, job:" JackMa"}; const handler={ get(trapTarget,property,receiver){ console.log(trapTarget===target); console.log(trapTarget[property]); console.log(receiver==proxy); return trapTarget[property]; } } console.log(proxy.name); // Return true JackMa true JackMa conosle.log(target.name); / / return JackMaCopy the code
All catchers can reconstruct the original operation based on their parameters, but not all catchers behave as simply as get(). So it’s not practical to write code by hand. It can actually be easily reconstructed by calling a method of the same name on the global Reflect object, which encapsulates the original behavior
All methods that can be captured in a handler object have corresponding Reflection API methods. These methods have the same name and function signature as the method oil of the catcher, and also have the same behavior as the intercepted method.
An example of reflection
Let person ={name:"JackMA", age:59, job:' JackMA '}; let handler={ get(){ return Reflect.get(... arguments); }}; const proxy=new Proxy(target,handler); console.log(proxy.name); // Return JackMa console.log(person.name); / / return JackMaCopy the code
9.1.4 Capture invariant
Using a catcher can change the behavior of almost any basic method, but it is not without limitations. According to the ECMAScipt specification, each capturing method knows the context of the target object and the capturing function signature. A capture handler must behave according to “trap invariant”
For example, if an object has another data attribute that is not writable and has a make-up configuration, TypeError is raised when the catcher returns a value different from that attribute
let person={}; Object.defineProperty(person,'name',{ configurable:false, writable:false, value:"JackMa" }); let handler={ get(){ return "XXX"; }}; let handler2={ get(){ return "JackMa"; } } let proxy=new Proxy(person,handler); console.log(proxy.name); // Return TypeError let proxy2=new Proxy(person,handler2); console.log(proxy2.name); / / return JackMaCopy the code
9.1.5 Revokeable proxy revoke() function
Sometimes a connection between a terminal proxy object and a target object may be required. For ordinary proxies created using new Proxy(), this connection persists for the lifetime of the Proxy object.
Proxy also exposes the Revocable () method, which supports unassociating the Proxy with the target object. Revoke () is idempotent, and results no matter how many times it is invoked. Calling the proxy after it has been revoked raises TypeError
const person={ name:'JackMa' }; const handler={ get(){ return 'this is a ret'; }}; const {proxy,revoke}=Proxy.revocable(person,handler); cosole.log(proxy.name); // Return this is a ret cosole.log(person.name); // Return JackMa REVOKE (); console.log(proxy.name); / / return TypeErrorCopy the code
9.1.6 Practical reflection API
There are reasons why reflection apis should be preferred in some cases.
Reflection API and object API
When using the reflection API, remember:
(1) Reflection apis are not limited to capture processing.
(2) Most reflection API methods have corresponding methods on type Object.
In general, methods on Object are suitable for general-purpose programs, while reflection methods are suitable for fine-grained Object controls and operations.
2. Status markers
Many reflection methods return a Boolean value called a “status flag” indicating whether the intended operation was successful. Sometimes, status flags are more useful than reflection API methods that return modified objects or throw errors (depending on the method).
For example, the following code could be refactored using the reflection API
// Initialize code const o={}; try{ Object.defineProperty(o,'name','JackMa'); console.log('sucess'); }catch(e){ console.log('failure'); } // return failureCopy the code
Reflect.defineproperty () returns false instead of throwing an error if a problem occurs while defining a new property. So the above code can be refactored using this reflection method
// Let person={}; if(Reflect.defineProperty(person,'name','JackMa')){ console.log('sucess! '); }else{ console.log('failure'); } // return sucess console.log(person.name); / / return JackMaCopy the code
The following reflection methods all provide status flags:
1, class methods Reflect. PreventExtensions (target)
This method prevents new attributes from being added to the object
let person={}; Reflect.preventExtension(person); if(Reflect.defineProperty(person,'name','JackMa')){ console.log('Sucess! '); }else{ console.log('failure! '); } // return failure!Copy the code
Reflect.deleteProperty(target,propertyKey);
9.1.7 Agent Another agent
Proxies can intercept the operations of the reflection API, but it is entirely possible to create a proxy that can be used to proxy another proxy, allowing multiple interceptors to be built on a single target.
let person={ name:"JackMa", age:59 }; let firstProxy=new Proxy(target,{ get(){ console.log('first Proxy'); return Reflect.get(... arguments); }}); Const secondProxy=new Proxy(firstProxy,{get(){console.log(' secondProxy '); return Reflect.get(... arguments); } }) console.log(secondProxy.name); // Return first Proxy JackMa for the second timeCopy the code
9.1.8 Problems and deficiencies of agency
1. This points to the problem in the proxy
This refers to the object calling this method
let person={ thisValEqu(){ return this===proxy; } } const proxy=new Proxy(person,{}); console.log(person.thisValEqu()); // Return false console.log(person.thisValequ ()); / / return trueCopy the code
2. Agent and internal slot
Proxy and built-in reference types, such as instances of Array, work well together. However, some ECMAScript built-in types may rely on mechanisms beyond the control of the agent, resulting in certain errors on the agent
let timi=new Date(); const proxy=new Proxy(timi,{}); console.log(proxy istance of Date); // Return true proxy.getDate(); //TypeErrorCopy the code
9.2 Proxy capture and reflection methods
The agent can capture 13 different operations that have different reflection API methods. Parameters associated with ECMAScript operations and invariants
9.2.1 the get ()
The GET () trap is called during the operation to get the property value. The corresponding reflection API method is reflect.get ()
let person={ name:"JackMa", } let proxy=new Proxy(person,{ get(tar,prop,rec){ console.log(tar); console.log(prop); console.log(receiver); console.log(tar[prop]); return Reflect.get(... arguments); }}); proxy.name; // Return Object name proxy JackMaCopy the code
1. Return value
Return value unlimited
2. Interception operation
4, Reflect.GET(proxy,property,receiver) 4, Reflect.
3. Trap handler parameters
** Target :** Target object ** Property :** Properties on the referenced target object ** Receiver :** Proxy objects or objects that inherit proxy objects
4. Capture invariant
If target.property is unwritable and unconfigurable, the value returned by the handler must match target.property
9.2.2 set ()
The set() trap is called during an operation to set the property value. The corresponding reflection API method is reflect.set ()
let person={ name="Timi" } let proxy=new Proxy(person,{ set(target,property,value,receiver){ console.log('set()'); return Reflect.set(... arguments); }}); proxy.name="JackMa"; // Return set() console.log(proxy.name); / / return JackMaCopy the code
1, return value return true indicates success; Returning false indicates failure, and TypeError is raised in strict mode
Property =value
proxy[property]=value
Object.create(proxy)[property]=value
Reflect.set(proxy,property,value,receiver);
3. Trap handler parameters
Target Target object Property on the object Value The value to be assigned Receiver The object that receives the initial assignment
9.2.3 has ()
The HAS () trap is called in the parent of the IN operation. The corresponding reflection API method is reflect.has ()
let person={ name:'JackMa', } let proxy=new Proxy(person,{ has(target,property){ console.log('Has'); return Reflect.has(... arguments); }}); console.log('JackMa' in proxy); // Return false console.log('name' in proxy); / / return trueCopy the code
9.2.4 defineProperty ()
DefineProperty () traps are called in Object.defineProperty(). The corresponding reflection API method is reflect.defineProperty ()
let person={ }; const proxy=new Proxy(person,{ defineProperty(target,property,descriptor){ console.log('defineProperty()'); return Reflect.defineProperty(... arguments) } }); Object.defineProperty(proxy,'name',{value:'JackMa'}); // Return true console.log(proxy.name); // return Jack MaCopy the code
9.2.5 getOwnPropertyDescriptor ()
9.3 Proxy Mode (Omitted for now)
Using agents allows you to implement some useful programming patterns in your code
9.3.1 Tracing property Access
By capturing operations such as GET, SET, and HAS, you can know when an object property is accessed and queried. Putting an object proxy that implements the corresponding catcher into your application allows you to monitor where that object is appropriate to be accessed