Javascript/ES6代理Proxy用法

  在ES6中,Proxy构造器是一种可访问的全局对象,使用它,你可以在对象与各种操作对象的行为之间收集有关请求操作的各种信息,并返回任何你想做的。在这点上,代理Proxy与中间件有很多共同点。也就是说,代理Proxy会代理你要操作的对象,那为什么要代理原对象呢?

Proxy代理能够让你截取原对象的各种操作方法,最普通的是get set apply和construct等方法,看这个规定有很多被拦截的方法.。Proxy代理也能配置为任何时候停止拦截请求,有效地撤销他们所服务的对象的所有访问代理,这是通过revoke方法实现的。

首先解释一下代理中使用到的技术名词:

target是指代理的原对象,它是你需要拦截访问的原始对象,它总是作为Proxy构造器的第一个方法,也可以传递到每个trap中。

handler是一个包含你要进行拦截和处理的对象,也就是你拦截了原始对象以后想干什么?主要的代理内容在这里,是作为Proxy构造器的第二个方法参数传统,它是实现Proxy API。

trap用来规定对于指定什么方法进行拦截处理,如果你想拦截get方法的调用,那么你要定义一个get trap。

基本用法

这里是一个Hello world用法的代码:

let dataStore = {  
  name: 'Billy Bob', 
  age: 15 
}; 
let handler = {  
  get(target, key, proxy) { //get 的trap 拦截get方法
    const today = new Date(); 
    console.log(`GET request made for ${key} at ${today}`); 
    return Reflect.get(target, key, proxy); //拦截get方法
  } 
} 
//构造一个代理,第一个参数是原始对象,第二个参数是代理处理器
dataStore = new Proxy(dataStore, handler);       
//这里将会执行我们的handler, 记录请求,设置name值。
const name = dataStore.name; 

 

下面总结一下Proxy具体应用用法模式:

1.剥离校验代码

我们经常有对数字进行校验的代码,现在可以将这部分校验代码放入Proxy中。

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); 
  } 
}); 
//这会抛出错误
numericDataStore.count = "foo"; 
//这会正常设置新值
numericDataStore.count = 333; 

 

如果你想对对象的所有属性都定制一个校验器,代码可能会复杂一些:

//定义一个校验器validator对象 返回一个proxy 
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 { 
        // prevent setting a property that isn't explicitly defined in the validator 
        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); //返回的是代理对象,不是真正Person原始对象
  } 
} 
      
const bill = new Person('Bill', 25);       
//下面都会抛错
bill.name = 0;  
bill.age = 'Bill';  
bill.age = 15;   

这种校验方法无需修改原来的代码,因为修改原来稳定的代码可能会引入新的Bug。

下面代码是检查传入每个执行方法的参数,以校验参数类型签名是否和要求的一致?

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[hey]; 
    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, {}); 

 

实现真正的Javascript私有

我们会使用下划线定义私有变量,但是你无法阻碍别人会偷偷读取它,下面apiKey是只能在方法中访问,不想被外面访问:

var api = {  
  _apiKey: '123abc456def', 
  /* mock methods that use this._apiKey */ 
  getUsers: function(){}, 
  getUser: function(userId){}, 
  setUser: function(userId, config){} 
}; 
// logs '123abc456def'; 
console.log("An apiKey we want to keep private", api._apiKey); 
// 会获得_apiKeys值
var apiKey = api._apiKey;  
api._apiKey = '987654321'; //修改了

 

使用代理你能拦截某个属性访问并限制返回undefined:

var api = {  
  _apiKey: '123abc456def', 
  /* mock methods that use this._apiKey */ 
  getUsers: function(){ }, 
  getUser: function(userId){ }, 
  setUser: function(userId, config){ } 
}; 
// 增加限制访问的属性在这个数组中
const RESTRICTED = ['_apiKey']; 
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); 
    } 
}); 
// throws an error 
console.log(api._apiKey); 
// throws an error 
api._apiKey = '987654321';   

 

也能用has这个trap进行拦截,与上面相同代码不再列出:

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...") 
  } 
} 

下页