ES6 Proxy怎么使用全剖析
ES6 Proxy
proxy者,代理也。我的理解,就是加壳,就是绿林、就是收费站。
假设一个函数,好比一条高速公路,可以从A地到B地。具备的功能就是联通。具备的属性就是名字、长度、几车道 等。如果没有代理,什么也不做,那么你不会对从他上面 通过的那些川流不息的车有任何影响。
如果代理(收费站)介入,将产生如下变化:
1.基础篇
通过收费站,才能上这条公路,收费站同时记录了你从A到B,还是从B到A 的信息。
定义这条高速公路
const road={name:"AB高速",width:8,length:300, direction:function(x,y){return x+"->"+y}}
建立代理
const roadhandler = { get(road, key, proxy) { const today = new Date(); console.log(`你访问了属性 ${key} 在 ${today}`); return Reflect.get(road, key, proxy); } }; const roadproxy = new Proxy(road, roadhandler);
试执行
roadproxy.name VM263:1 你访问了属性 name 在 Thu Nov 01 2018 10:48:09 GMT+0800 (中国标准时间) "AB高速"
roadproxy.direction("a","b") VM263:1 你访问了属性 direction 在 Thu Nov 01 2018 10:53:47 GMT+0800 (中国标准时间) "a->b"
roadhandler 就是那个收费站,要想用这条路,就得先经过他,然后才能上。
例子一就是通过他看了看,这条路是不是你想要走的
例子二就是上去走,你要从a走到b.
2.抽离校验模块
我申请了一笔经费,要把这条路的宽度改一下,此时各家就要上报,改成的具体宽是多少,但也有不按约定报的,我们为了防止误报上要校验。
let numericDataStore = { width: 8 }; numericDataStore = new Proxy(numericDataStore, { set(target, key, value, proxy) { if (typeof value !== 'number') { throw Error("只能上报数字类型,别的不接受。"); } return Reflect.set(target, key, value, proxy); } });
试用一下:
numericDataStore.width="1" VM314:1 Uncaught Error: 只能上报数字类型,别的不接受。 at Object.set (<anonymous>:1:159) at <anonymous>:1:23 set @ VM314:1 (anonymous) @ VM336:1
numericDataStore.width=12 12
更复杂的校验
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);
3.私有属性 使对象的私有属性不能被外部访问到
在 JavaScript 或其他语言中,大家会约定俗成地在变量名之前添加下划线 _ 来表明这是一个私有属性(并不是真正的私有),但我们无法保证真的没人会去访问或修改它。在下面的代码中,我们声明了一个私有的 apiKey,便于 api 这个对象内部的方法调用,但不希望从外部也能够访问 api._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); // get and mutate _apiKeys as desired var apiKey = api._apiKey; api._apiKey = '987654321';
很显然,约定俗成是没有束缚力的。使用 ES6 Proxy 我们就可以实现真实的私有变量了,下面针对不同的读取方式演示两个不同的私有化方法。第一种方法是使用 set / get 拦截读写请求并返回 undefined:
let api = { _apiKey: '123abc456def', 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); } }); // 以下操作都会抛出错误 console.log(api._apiKey); api._apiKey = '987654321';
var api = { _apiKey: '123abc456def', getUsers: function() {}, getUser: function(userId) {}, setUser: function(userId, config) {} }; const RESTRICTED = ['_apiKey']; 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...") } }
4.访问日志 这个很好理解,就是记录一下你访问了对象的哪些属性
对于那些调用频繁、运行缓慢或占用执行环境资源较多的属性或接口,开发者会希望记录它们的使用情况或性能表现,这个时候就可以使用 Proxy 充当中间件的角色,轻而易举实现日志功能:
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();
5.预警和拦截
假设你不想让其他开发者删除 noDelete 属性,还想让调用 oldMethod 的开发者了解到这个方法已经被废弃了,或者告诉开发者不要修改 doNotChange 属性,那么就可以使用 Proxy 来实现:
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();
6. 过滤操作
某些操作会非常占用资源,比如传输大文件,这个时候如果文件已经在分块发送了,就不需要在对新的请求作出相应(非绝对),这个时候就可以使用 Proxy 对当请求进行特征检测,并根据特征过滤出哪些是不需要响应的,哪些是需要响应的。下面的代码简单演示了过滤特征的方式,并不是完整代码,相信大家会理解其中的妙处:
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); } } });
7.中断代理
Proxy 支持随时取消对 target 的代理,这一操作常用于完全封闭对数据或接口的访问。在下面的示例中,我们使用了 Proxy.revocable 方法创建了可撤销代理的代理对象:
let sensitiveData = { username: 'devbryce' }; const { sensitiveData, revokeAccess } = Proxy.revocable(sensitiveData, handler); function handleSuspectedHack() { revokeAccess(); } // logs 'devbryce' console.log(sensitiveData.username); handleSuspectedHack(); // TypeError: Revoked console.log(sensitiveData.username);