Proxy

What is the Proxy

I want to make it very simple here, Proxy is a Proxy for an object, it makes a lot of sense, we have an important object, we don’t want people to pick it up and modify it, we want to protect it, Proxy it with another object, it’s a kind of data protection and filtering for the object that we really want to manipulate.

Proxy does not simply protect objects through common code forms such as if else. It protects objects at the level of code programming, belonging to “meta-programming”. For example, we can use get() set() as a proxy when reading or setting properties of objects, use apply() when executing functions (which are inherently objects), and intercept functions as constructors using constructor.

What interception operations are performed by Proxy

As a Proxy, it cannot be left to programmers to “do what they want”. Therefore, IN ES6, there are some restrictions on Proxy interception operations.

  • Get (Target, propKey, receiver): Intercepts reading of object properties
  • Set (Target, propKey, value, receiver): Intercepts the setting of object properties
  • From the (target, propKey) : interceptionpropKey in proxyOperation that returns a Boolean value
  • DeleteProperty (target, propKey) : interceptiondelete proxy[propKey]Returns a Boolean value
  • OwnKeys (target) : interceptionObject.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy),for... inLoop to return an array. This method returns the property names of all of the target Object’s own properties, whereas object.keys () returns only the traversable properties of the target Object itself.
  • GetOwnPropertyDescriptor (target, propKey) : interception Object. GetOwnPropertyDescriptor (proxy, propKey), returns the attributes describe objects.
  • DefineProperty (target, propKey propDesc) : Intercepts Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDescs), and returns a Boolean value.
  • PreventExtensions (target) : Intercepts Object.preventExtensions(proxy), returns a Boolean.
  • GetPrototypeOf (target) : Intercepts object.getProtoTypeof (proxy) and returns an Object.
  • IsExtensible (Target) : Intercepts Object. IsExtensible (proxy), returning a Boolean value.
  • SetPrototypeOf (target, proto) : Intercepts Object.setPrototypeOf(proxy, proto) and returns a Boolean value. If the target object is a function, there are two additional operations that can be intercepted.
  • Apply (target, object, args) : intercepts operations called by Proxy instances as functions, such as Proxy (… The args), proxy. Call (object,… The args), proxy. Apply (…). .
  • Construct (target, args) : intercepts operations called by Proxy instances as constructors, such as new Proxy (… The args).
var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]}; }};var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1.2) / / 1
new fproxy(1.2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
Copy the code

The application of the Proxy

Proxy provides a variety of interceptors. You can combine these interceptors according to specific requirements to achieve your own purpose

  • Prevents internal attributes (private attributes) of an object from being read or written externally to achieve type safety

Private properties in JS objects are not specified. We have a convention to use the prefix “” to represent private properties, but still not” private “. You can use Proxy to do private attributes. In get/set attributes, check whether the first character is ”, if so, it will generate an error.

const handle = {
    get(target, p, receiver) {
        inver(p, 'get')
        return target[p]
    },
    set(target, p, value, receiver) {
        inver(p, 'set')
        target[p] = value
        return true; }}function inver(key, action) {
    if (key[0= = ='_') {
        throw new Error(`Invalid attempt to ${action} ${key}`)}}try {
    const target = {}
    const proxy = new Proxy(target, handle)
    proxy._prop
    proxy._prop = 'c'
} catch (e) {
    console.log(e) // Error: Invalid attempt to get private "_prop" property
}

Copy the code
  • usehasMethod to hide certain properties from beinginOperator discovery

var handler = {
  has (target, key) {
    if (key[0= = ='_') {
      return false;
    }
    return key intarget; }};var target = { _prop: 'foo'.prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
Copy the code
  • Interrupt handlingProxy.revocable()

The proxy. revocable method returns a cancelable Proxy instance

let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo / / 123

revoke();
proxy.foo // TypeError: Revoked
Copy the code

The proxy. revocable method returns an object whose Proxy property is a Proxy instance. Revoke property is a function that cancels a Proxy instance. In the above code, when accessing the Proxy instance after executing revoke, an error is thrown.

One use scenario for proxy.revocable is where the target object is not allowed to be accessed directly, but must be accessed through a Proxy, and once the access is complete, the Proxy is revoked and no further access is allowed.

  • Pull out the verification module and check the type

let numericDataStore = {  
    count: 0,
    amount: 1234,
    total: 14
};

numericDataStore = new Proxy(numericDataStore, {  
    set(target, key, value, proxy) {
        if(typeof value ! = ='number') {
            throw Error("Properties in numericDataStore can only be numbers");
        }
        returnReflect.set(target, key, value, proxy); }}); // Throw an error because"foo"Not numericdatastore.count ="foo"; Numericdatastore. count = 333;Copy the code

Check out my other translation article: using ES6 Proxy for type safety in JavaScript allows dynamic type checking

  • Access log

For those attributes or interfaces that are frequently invoked, run slowly, or occupy a lot of resources in the execution environment, developers want to record their usage or performance. In this case, Proxy can be used as the role of middleware to easily implement the logging function:

let api = {  
    _apiKey: '123abc456def'.getUsers: function() { / *... * / },
    getUser: function(userId) { / *... * / },
    setUser: function(userId, config) { / *... * /}};function logMethodAsync(timestamp, method) {  
    setTimeout(function() {
        console.log(`${timestamp} - Logging ${method} request asynchronously.`);
    }, 0)
}

api = new Proxy(api, {  
    get: function(target, key, proxy) {
        var value = target[key];
        return function(. arguments) {
            logMethodAsync(new Date(), key);
            return Reflect.apply(value, target, arguments); }; }}); api.getUsers();Copy the code
  • Warning and interception

If you don’t want other developers to delete the noDelete attribute, and you want the developer calling oldMethod to know that the method is deprecated, or to tell the developer not to change the doNotChange attribute, you can use Proxy to do this:

let dataStore = {  
    noDelete: 1235.oldMethod: function() {/ *... * / },
    doNotChange: "tried and true"
};

const NODELETE = ['noDelete'];  
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod'];  

dataStore = new Proxy(dataStore, {  
    set(target, key, value, proxy) {
        if (NOCHANGE.includes(key)) {
            throw Error(`Error! ${key} is immutable.`);
        }
        return Reflect.set(target, key, value, proxy);
    },
    deleteProperty(target, key) {
        if (NODELETE.includes(key)) {
            throw Error(`Error! ${key} cannot be deleted.`);
        }
        return Reflect.deleteProperty(target, key);

    },
    get(target, key, proxy) {
        if (DEPRECATED.includes(key)) {
            console.warn(`Warning! ${key} is deprecated.`);
        }
        var val = target[key];

        return typeof val === 'function' ?
            function(. args) {
                Reflect.apply(target[key], target, args); } : val; }});// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo";  
delete dataStore.noDelete;  
dataStore.oldMethod();
Copy the code
  • A slightly operation

Certain operations can be very resource-intensive, transferring large files, for example, if the file has been sent out on partitioned by this time, there is no need for the new request (not absolute) accordingly, this time can use Proxy get request for feature detection, and according to the characteristics of filter out which is not to need to response, which is the need to respond to. The following code is a simple example of how to filter features. It is not the complete code, but you will understand the beauty of it:

let obj = {  
    getGiantFile: function(fileId) {/ *... * /}}; obj =new Proxy(obj, {  
    get(target, key, proxy) {
        return function(. args) {
            const id = args[0];
            let isEnroute = checkEnroute(id);
            let isDownloading = checkStatus(id);      
            let cached = getCached(id);

            if (isEnroute || isDownloading) {
                return false;
            }
            if (cached) {
                return cached;
            }
            return Reflect.apply(target[key], target, args); }}});Copy the code

As you can see from the above, the Proxy is very useful for the object Proxy, can only show the content that we allow to show, such as certain properties, certain methods. Proxys is metaprogramming and is used in framework writing. If the framework is complex, some of the encapsulated objects must be private and cannot be exposed to the outside world. Therefore, Proxys can be used to ensure the security and independence of the encapsulated objects.

Even if it is not in the framework, it can be used in our normal development tasks. Such as encapsulating database ORMs, proxy network requests, and so on.

Consider: Proxy and TypeScript relationships and differences

Like the set() intercept, we can intercept whether the value type meets our requirements, such as the value must be numeric for the set() operation to succeed. Proxy and TypeScript can do the same. So what’s the difference? Proxy is similar to “metaprogramming,” whereas TypeScript is a superset of JavaScript types that can be compiled into JS. They solve problems at different levels. TypeScript is static type checking, which can be detected at compile time, and the IDE can report errors for us. Proxy can provide dynamic type checking and can do it at run time.

Reflect

Reflect is a built-in object that provides methods to intercept JavaScript operations. These methods are the same as the proxy Handlers method. Reflect is not a function object, so it is not constructible.

describe

Unlike most global objects, Reflect is not a constructor. You cannot use it with a new operator, or call the Reflect object as a function. All attributes and methods of Reflect are static (just like Math objects).

methods

Reflect is a built-in object that you can type in the browser console to view:

The Reflect object provides the following static functions that have the same name as the methods on the processor object (that is, Proxy Handle). Some of these methods are the same as their counterparts on Object.

  • Reflect.apply() calls a function and can pass in an array as the call argument. This is similar to function.prototype.apply ().
  • Reflect.construct() applies a new operation to the constructor, which is equivalent to executing a new target(… The args).
  • Reflect.defineproperty () is similar to object.defineProperty ().
  • Reflect.deleteproperty () is the delete operator of the function, equivalent to the delete target[name].
  • Reflect.enumerate() This method returns an iterator containing all the enumerable properties of the target’s own string as well as inherited string properties, for… It is these properties that the IN operation traverses.
  • Reflect.get() gets the value of an attribute on the object, similar to target[name].
  • Reflect. GetOwnPropertyDescriptor () is similar to the Object. GetOwnPropertyDescriptor (). Reflect.getprototypeof () is similar to object.getPrototypeof (). Reflect.has() determines whether an object has an attribute, in exactly the same way as the in operator. Reflect.isextensible () is similar to object.isextensible ().reflect.ownkeys () returns an array containing all its own properties (excluding inherited properties). (similar to the Object. The keys (), but it will not be affected by enumerable). Reflect the preventExtensions () is similar to the Object. The preventExtensions (). Return a Boolean. Reflect.set() the function that assigns values to attributes. Returns a Boolean, true if the update succeeded. Reflect.setprototypeof () is similar to object.setprototypeof ().

Reference links:

Developer.mozilla.org/zh-CN/docs/…

medium.com/@SylvainPV/…