Home >Web Front-end >JS Tutorial >Instructions for using Proxy in ES6

Instructions for using Proxy in ES6

亚连
亚连Original
2018-06-13 10:57:351600browse

This article mainly introduces the usage scenarios of ES6 Proxy. Now I will share it with you and give you a reference.

Features such as arrow functions, array destructuring, and rest parameters in ES6 have been widely circulated as soon as they were implemented. However, features like Proxy are rarely used by developers. On the one hand, it is due to browser compatibility. On the other hand, in order to take advantage of these features, developers need to deeply understand their usage scenarios. Personally, I like ES6's Proxy very much because it allows us to control external access to objects in a concise and easy-to-understand way. In the following, I will first introduce how to use Proxy, and then explain the usage scenarios of Proxy by citing specific examples.

Proxy, as the name implies, its function is very similar to the proxy mode in the design pattern. This mode is often used in three aspects:

  1. Interception and monitoring of external requests Object access

  2. Reduce the complexity of functions or classes

  3. Verify operations or manage required resources before complex operations

In a browser environment that supports Proxy, Proxy is a global object and can be used directly. Proxy(target, handler) is a constructor, target is the object being proxied, handler is an object that declares various proxy operations, and ultimately returns a proxy object. Every time the outside world accesses the properties of the target object through the proxy object, it will pass through the handler object. From this process, the proxy object is very similar to middleware. So what operations can Proxy intercept? The most common operations are get (read), set (modify) object attributes and other operations. For a complete list of interceptable operations, please click here. In addition, the Proxy object also provides a revoke method to log out all proxy operations at any time. Before we formally introduce Proxy, it is recommended that you have a certain understanding of Reflect. It is also a new global object in ES6. For detailed information, please refer to MDN Reflect.

Basic

const target = { 
  name: 'Billy Bob',
  age: 15
};

const handler = { 
  get(target, key, proxy) {
    const today = new Date();
    console.log(`GET request made for ${key} at ${today}`);

    return Reflect.get(target, key, proxy);
  }
};

const proxy = new Proxy(target, handler);
proxy.name;
// => "GET request made for name at Thu Jul 21 2016 15:26:20 GMT+0800 (CST)"
// => "Billy Bob"

In the above code, we first define a proxy target object target, then declare the handler object that contains all proxy operations, and then use Proxy(target, handler) creates a proxy object proxy. After that, all accesses to the target attribute using proxy will be processed by the handler.

1. Extract verification module

Let us start with a simple type verification. This example demonstrates how to use Proxy to ensure the accuracy of data types. Characteristics:

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");
    }
    return Reflect.set(target, key, value, proxy);
  }
});

// 抛出错误,因为 "foo" 不是数值
numericDataStore.count = "foo";

// 赋值成功
numericDataStore.count = 333;

If you want to directly develop a validator for all properties of an object, the code structure may quickly become bloated. Using Proxy, you can separate the validator from the core logic and make it self-contained. :

function createValidator(target, validator) { 
  return new Proxy(target, {
    _validator: validator,
    set(target, key, value, proxy) {
      if (target.hasOwnProperty(key)) {
        let validator = this._validator[key];
        if (!!validator(value)) {
          return Reflect.set(target, key, value, proxy);
        } else {
          throw Error(`Cannot set ${key} to ${value}. Invalid.`);
        }
      } else {
        throw Error(`${key} is not a valid property`)
      }
    }
  });
}

const personValidators = { 
  name(val) {
    return typeof val === 'string';
  },
  age(val) {
    return typeof age === 'number' && age > 18;
  }
}
class Person { 
  constructor(name, age) {
    this.name = name;
    this.age = age;
    return createValidator(this, personValidators);
  }
}

const bill = new Person('Bill', 25);

// 以下操作都会报错
bill.name = 0; 
bill.age = 'Bill'; 
bill.age = 15;

By separating the validator and the main logic, you can infinitely expand the content of the personValidators validator without causing direct damage to related classes or functions. To make it more complicated, we can also use Proxy to simulate type checking and check whether the function receives the correct type and number of parameters:

let obj = { 
  pickyMethodOne: function(obj, str, num) { /* ... */ },
  pickyMethodTwo: function(num, obj) { /*... */ }
};

const argTypes = { 
  pickyMethodOne: ["object", "string", "number"],
  pickyMethodTwo: ["number", "object"]
};

obj = new Proxy(obj, { 
  get: function(target, key, proxy) {
    var value = target[key];
    return function(...args) {
      var checkArgs = argChecker(key, args, argTypes[key]);
      return Reflect.apply(value, target, args);
    };
  }
});

function argChecker(name, args, checkers) { 
  for (var idx = 0; idx < args.length; idx++) {
    var arg = args[idx];
    var type = checkers[idx];
    if (!arg || typeof arg !== type) {
      console.warn(`You are incorrectly implementing the signature of ${name}. Check param ${idx + 1}`);
    }
  }
}

obj.pickyMethodOne(); 
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 1
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 2
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 3

obj.pickyMethodTwo("wopdopadoo", {}); 
// > You are incorrectly implementing the signature of pickyMethodTwo. Check param 1

// No warnings logged
obj.pickyMethodOne({}, "a little string", 123); 
obj.pickyMethodOne(123, {});

2. Private properties

In In JavaScript or other languages, it is a common practice to add an underscore _ before a variable name to indicate that it is a private property (not really private), but we cannot guarantee that no one will access or modify it. In the following code, we declare a private apiKey to facilitate method calls within the api object, but we do not want to be able to access the api from the outside._apiKey:

var api = { 
  _apiKey: &#39;123abc456def&#39;,
  /* mock methods that use this._apiKey */
  getUsers: function(){}, 
  getUser: function(userId){}, 
  setUser: function(userId, config){}
};

// logs &#39;123abc456def&#39;;
console.log("An apiKey we want to keep private", api._apiKey);

// get and mutate _apiKeys as desired
var apiKey = api._apiKey; 
api._apiKey = &#39;987654321&#39;;

Obviously, the convention is not binding of. Using ES6 Proxy we can implement real private variables. The following demonstrates two different privatization methods for different reading methods. The first method is to use set/get to intercept read and write requests and return undefined:

let api = { 
  _apiKey: &#39;123abc456def&#39;,
  getUsers: function(){ }, 
  getUser: function(userId){ }, 
  setUser: function(userId, config){ }
};

const RESTRICTED = [&#39;_apiKey&#39;];
api = new Proxy(api, { 
  get(target, key, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
      throw Error(`${key} is restricted. Please see api documentation for further info.`);
    }
    return Reflect.get(target, key, proxy);
  },
  set(target, key, value, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
      throw Error(`${key} is restricted. Please see api documentation for further info.`);
    }
    return Reflect.get(target, key, value, proxy);
  }
});

// 以下操作都会抛出错误
console.log(api._apiKey);
api._apiKey = &#39;987654321&#39;;

The second method is to use has to intercept in operations:

var api = { 
  _apiKey: &#39;123abc456def&#39;,
  getUsers: function(){ }, 
  getUser: function(userId){ }, 
  setUser: function(userId, config){ }
};

const RESTRICTED = [&#39;_apiKey&#39;];
api = new Proxy(api, { 
  has(target, key) {
    return (RESTRICTED.indexOf(key) > -1) ?
      false :
      Reflect.has(target, key);
  }
});

// these log false, and `for in` iterators will ignore _apiKey
console.log("_apiKey" in api);

for (var key in api) { 
  if (api.hasOwnProperty(key) && key === "_apiKey") {
    console.log("This will never be logged because the proxy obscures _apiKey...")
  }
}

3. Access log

For those properties or interfaces that are called frequently, run slowly, or occupy a lot of execution environment resources, developers will want to record their usage or performance. At this time, Proxy can be used to act as middleware. Role, it is easy to implement the logging function:

let api = { 
  _apiKey: &#39;123abc456def&#39;,
  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();

4. Early warning and interception

Suppose you don’t want other developers to delete the noDelete attribute, and you also want developers to call oldMethod After learning that this method has been abandoned, or telling the developer not to modify the doNotChange attribute, you can use Proxy to implement it:

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

const NODELETE = [&#39;noDelete&#39;]; 
const NOCHANGE = [&#39;doNotChange&#39;];
const DEPRECATED = [&#39;oldMethod&#39;]; 

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 === &#39;function&#39; ?
      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();

5. Filtering operation

Someone Some operations will occupy a lot of resources, such as transferring large files. At this time, if the file is already being sent in chunks, there is no need to respond to new requests (not absolute). At this time, you can use Proxy to perform feature detection on the current request. , and filter out which ones do not require response and which ones do need to respond based on the characteristics. The following code simply demonstrates the method of filtering features. It is not a complete code. I believe everyone 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);
    }
  }
});

6. Interrupt proxy

Proxy support Cancel the proxy for the target at any time. This operation is often used to completely close access to data or interfaces. In the following example, we use the Proxy.revocable method to create a proxy object for a revocable proxy:

let sensitiveData = { username: &#39;devbryce&#39; };
const {sensitiveData, revokeAccess} = Proxy.revocable(sensitiveData, handler);
function handleSuspectedHack(){ 
  revokeAccess();
}

// logs &#39;devbryce&#39;
console.log(sensitiveData.username);
handleSuspectedHack();
// TypeError: Revoked
console.log(sensitiveData.username);

Decorator

The Decorator implemented in ES7 is equivalent to the decorator pattern in the design pattern. If we simply distinguish the usage scenarios of Proxy and Decorator, it can be summarized as follows: The core function of Proxy is to control the external access to the interior of the proxy, and the core function of Decorator is to enhance the function of the decorator. As long as their core usage scenarios are distinguished, functions such as access logs, although this article uses Proxy to implement them, can also be implemented using Decorator. Developers can implement them based on project needs, team specifications, and their own preferences. Freedom of choice.

The above is what I compiled for everyone. I hope it will be helpful to everyone in the future.

Related articles:

How to introduce public css files through vue

What are the methods of using ajax in Vue?

How to implement data distribution slot in vue.js

The above is the detailed content of Instructions for using Proxy in ES6. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn