Creating a proxy object

The Proxy is created using the Proxy constructor. This constructor takes two parameters: the target object and the handler object. Missing any of these parameters raises TypeError. To create an empty proxy, you can pass a simple object literal as a handler object, allowing all operations to reach the target object unimpeded.

const target = {
 id: 'target'
};
const handler = {};
const proxy = new Proxy(target, handler);
Copy the code

Define the catcher (concept from the operating system)

The main purpose of using proxies is that you can define traps. A catcher is an “interceptor for basic operations” defined in a handler object. Each handler object can contain zero or more traps, each of which corresponds to a basic operation that can be invoked directly or indirectly on a proxy object. Each time these basic operations are invoked on a proxy object, the proxy can intercept and modify the corresponding behavior by calling the catcher function before the operations are propagated to the target object. Similar to object.defineProperty () before

const target = { foo: 'bar' }; Const Handler = {// The catcher takes the method name key get() {return 'handler override' in the handler object; }}; const proxy = new Proxy(target, handler); console.log(target.foo); // bar console.log(proxy.foo); // handler override console.log(target['foo']); // bar console.log(proxy['foo']); // handler override console.log(Object.create(target)['foo']); // bar console.log(Object.create(proxy)['foo']); // handler overrideCopy the code

The capture parameter and the Reflect API

All catchers have access to parameters based on which the original behavior of the captured method can be reconstructed. For example, the GET () trap receives the target object, the property to be queried, and the proxy object. With these parameters, you can reconstruct the original behavior of the captured method

const target = { foo: 'bar' }; const handler = { get(trapTarget, property, receiver) { return trapTarget[property]; }}; const proxy = new Proxy(target, handler); console.log(proxy.foo); // bar console.log(target.foo); // barCopy the code

All catchers can reconstruct the original operation based on their parameters, but not all catchers behave as simply as get(). For this reason, the idea of doing the same thing by writing code by hand is unrealistic. Instead of manually rebuilding the original behavior, developers can easily rebuild it by calling a method of the same name on the global Reflect object that 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 methods intercepted by the catcher, and they also have the same behavior as the intercepted methods.

const target = { foo: 'bar' }; const handler = { get() { return Reflect.get(... arguments); }}; // const handler = {// get: reflect.get //}; const proxy = new Proxy(target, handler); console.log(proxy.foo); // bar console.log(target.foo); // barCopy the code

In fact, if you really wanted to create an empty proxy that could catch all methods and then forward each method to the corresponding reflection API, you wouldn’t even need to define a handler object:

const target = {
 foo: 'bar'
};
const proxy = new Proxy(target, Reflect);
console.log(proxy.foo); // bar
console.log(target.foo); // bar 
Copy the code

Reflection API versus object API

When using the reflection API, keep in mind: (1) the reflection API is not limited to capture handlers; (2) Most reflection API methods have corresponding methods on the Object type. In general, methods on objects are suitable for general-purpose programs, while reflection methods are suitable for fine-grained Object control and manipulation.

Many reflection methods return a Boolean value called a “status flag” that indicates whether the intended operation was successful.

The following reflection methods all provide status flags:

 Reflect. DefineProperty ()

 Reflect. PreventExtensions ()

 Reflect. SetPrototypeOf ()

 Reflect. The set ()

 Reflect. DeleteProperty ()

The following reflection methods provide operations that can only be done by an operator.

 reflect.get () : Can replace the object property access operator.

 reflect.set () : Can replace the = assignment operator.

 reflect.has () : can replace the in operator or with().

 reflect.deleteProperty () : can replace the delete operator.

 reflect.construct () : can replace the new operator.

The proxy pattern

Using agents allows you to implement some useful programming patterns in your code

Trace 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 when and where the object has been accessed:

const user = { name: 'Jake' }; const proxy = new Proxy(user, { get(target, property, receiver) { console.log(`Getting ${property}`); return Reflect.get(... arguments); }, set(target, property, value, receiver) { console.log(`Setting ${property}=${value}`); return Reflect.set(... arguments); }}); proxy.name; // Getting name proxy.age = 27; // Setting age=27Copy the code

Hidden attribute

const hiddenProperties = ['foo', 'bar'];
const targetObject = {
 foo: 1,
 bar: 2,
 baz: 3
};
const proxy = new Proxy(targetObject, {
 get(target, property) {
 if (hiddenProperties.includes(property)) {
 return undefined;
 } else {
 return Reflect.get(...arguments);
 }
 },
 has(target, property) { 
if (hiddenProperties.includes(property)) {
 return false;
 } else {
 return Reflect.has(...arguments);
 }
 }
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true 
Copy the code

Property verification

const target = { onlyNumbersGoHere: 0 }; const proxy = new Proxy(target, { set(target, property, value) { if (typeof value ! == 'number') { return false; } else { return Reflect.set(... arguments); }}}); proxy.onlyNumbersGoHere = 1; console.log(proxy.onlyNumbersGoHere); // 1 proxy.onlyNumbersGoHere = '2'; console.log(proxy.onlyNumbersGoHere); / / 1Copy the code

Function and constructor parameter validation

function median(... nums) { return nums.sort()[Math.floor(nums.length / 2)]; } const proxy = new Proxy(median, { apply(target, thisArg, argumentsList) { for (const arg of argumentsList) { if (typeof arg ! == 'number') { throw 'Non-number argument provided'; } } return Reflect.apply(... arguments); }}); console.log(proxy(4, 7, 1)); // 4 console.log(proxy(4, '7', 1)); // Error: Non-number argument providedCopy the code

Data binding and observables

A proxy lets you link together parts of the runtime that would otherwise be unrelated. This allows you to implement patterns that allow different code to interoperate. For example, you can bind the proxied class to a global instance collection and have all created instances added to the collection:

const userList = [];
class User {
 constructor(name) {
 this.name_ = name;
 }
}
const proxy = new Proxy(User, {
 construct() {
 const newUser = Reflect.construct(...arguments);
 userList.push(newUser);
 return newUser;
 }
});
new proxy('John');
new proxy('Jacob');
new proxy('Jingleheimerschmidt');
console.log(userList); // [User {}, User {}, User{}] 
Copy the code

Alternatively, you can bind the collection to an event dispatcher that sends a message each time a new instance is inserted:

const userList = [];
function emit(newValue) {
 console.log(newValue);
}
const proxy = new Proxy(userList, {
 set(target, property, value, receiver) {
 const result = Reflect.set(...arguments);
 if (result) {
 emit(Reflect.get(target, property, receiver));
 }
 return result;
 }
});
proxy.push('John');
// John
proxy.push('Jacob');
// Jacob
Copy the code