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);