Proxy简介
proxy可以理解成,在目标对象之前架设一层‘拦截’,外界对该对象的访问,都必须先通过这层拦截,因此提供一种机制,可以对外界进行过滤和改写。
ES6原生提供Proxy构造函数,用来生成Proxy实例
var proxy = new Proxy(target,handle);
new Proxy()表示生成一个proxy实例,target参数表示所要拦截的目标对象,handle参数也是一个对象,用来定制拦截行为。以下为具体事例
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = new Person('星星', 10);
// 通过Proxy代理了p1的访问
let p2 = new Proxy(p1, {
get(target, name) {
// target:被代理的对象
// name : 当前被访问的属性
if (name == 'age') {
return '保密';
}
return target[name];
},
set(target, name, value) {
// target : 被代理的对象
// name : 设置的属性名
// value : 值
if (name == 'age') {
console.log('不允许设置');
return;
}
target[name] = value;
}
});
p2.name = '星星';
p2.age = 1000;
console.log(p2.age);
console.log(p2.name);
从上述例子可以得知:设置代理后,要使proxy起作用,必须针对proxy实例(上述为p2)进行操作,而不是针对于目标对象(上述为p1)进行操作。注意,如果handler没有设置任何拦截,就等同于直接通向元对象。
一个技巧是将Proxy对象,设置到object.proxy属性上,从而可在object对象上调用。
var object = {proxy:new Proxy(target,handler);
Proxy实例也可以作为其他对象的原型对象。
同一拦截器函数,可以设置拦截多个操作。对于可以设置但是没有设置拦截的操作,在直接落在目标对象上,按照原先的方式产生结果。
Proxy常用方法
get()
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(即this关键字指向的那个对象),其中最后一个参数可选。
get: function(target, property, receiver) {}
如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错。当返回值是Reflect.get时,且目标属性是getter,这个参数是否传给Reflect.get,结果会不一样。具体来说,将参数三作为Reflect.get的参数三使用,遇见getter在取子属性时,会触发代理,因为此时Reflect.get中的this将指向参数三。而若没有,则不触发代理。
set()
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
set: function(obj, prop, value, receiver) {}
注意,如果目标对象自身的某个属性,不可写也不可配置,那么set不得改变这个属性的值,只能返回同样的值,否则报错。
apply
apply方法拦截函数的调用、call和apply操作。apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
var handler = {
apply (target, ctx, args) {
return Reflect.apply(...arguments);
}
};
- 在proxy的实例作为函数调用时,就会被apply拦截,此时apply可以不传参。
- 当执行proxy函数(直接调用或call和apply调用),就会被apply方法拦截。
- 直接调用Reflect.apply方法,也会被拦截。注:Reflect API:反射对象信息,通过这个对象可以获取到某个对象的所有元信息数据,在使用Proxy时,使用本方法效果比直接通过对象属性调用效果更好。
construct
construct用来拦截new命令。construct接收两个参数:target:目标对象,args:构建函数的参数对象。construct必须也是一个对象,否则报错。
var handler = {
construct (target, args, newTarget) {
return new target(...args);
}
};
更多例子可以查看阮一峰老师的博客
Proxy的应用
数据校验
proxy可以用来将校验代码与核心代码进行分离,进一步优化代码。下面是一个数据校验的示例:
//定义一个接收自定义校验规则并返回一个proxy的校验器
function checkOperation(target,rules){
return new Proxy(target,{
_rules:rules,
set(target,key,value,proxy){
if(target.hasOwnProperty(key)){
let rules = this._rules[key];//根据传入的key名确认采用哪个自定义校验规则
if(!!rules(value)){
//双感叹号的作用在于如果明确设定了变量值(非0/undefined/null/""等值),结果会按照变量的实际值进行返回。否则,返回值一律为false。这样能够较将判断的精确度提高
return Reflect.set(target, key, value, proxy);
}else{
throw Error(`Cannot set ${key} to ${value}. Invalid.`);
}
}else{
throw Error(`${key} is an invalid property`);
}
}
})
}
//定义校验规则,可以在不影响功能代码的前提下,按照需求自定义规则
const checkRule = {
name(val){
return typeof val ==='string';
age(val){
return typeof val === 'number'&&val>0
}
}
}
//定义一个person类,返回值为校验后的结果
class Person{
construct(name,age){
this.name = name;
this.age = age;
return checkOperation(this,checkRule);
}
}
const stars = new Person('星星', 23);
//以下设置都会产生报错
stars.name = 23;
stars.age = '星星';
实现js中真正的私有属性
我们可以通过proxy中的get方法,来设定一个对象的某些属性不可访问,即设置对该属性进行访问时,返回undefined或者按需做出限制。示例如下:
let person = {
name:'星星',
age:23,
income:9000,
}
//将限制操作的属性名存入一个数组内
let secrety = ['income'];
person = proxy(person,{
get(target, key, proxy){
if(secrety.indexOf(key)>-1){
throw Error(`${key}不允许被访问`)
}
return Reflect.get(target,key,proxy);
}
set(target,key,val,proxy){
if(secrety.indexOf(key)>-1){
throw Error(`${key}不允许被修改`);
}
return Reflect.get(target,key,val,proxy);
}
})
也可以使用has方法,来‘隐藏’该属性。has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。但是如果原对象不可配置或禁止扩展,has拦截会报错。也就是说如果某个属性不可配置(或目标对象不可扩展),则has方法是不能‘隐藏’目标对象的该属性。因此,可将上述示例修改为
let person = {
name:'星星',
age:23,
income:9000,
}
//将限制操作的属性名存入一个数组内
let secrety = ['income'];
person = proxy(person,{
has(target,key){
return (secrety.indexOf(key)>-1)?false:Reflect.has(target,key);
}
});
console.log('income' in person);//打印结果为false
for (var key in person) {
if (person.hasOwnProperty(key) && key === "income") {
console.log(person[key]);//可成功打印出income的属性值
}
为什么最后还能打印出income的属性值,这是因为has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,而且它对in操作符有效,但是对for…in…操作拦截无效
可记录对象的访问
proxy可以在后台对一些你想要监测的接口进行数据监测。
给出提示信息或是阻止特定操作
这个作用的操作类似于第二个作用,只是第二个作用-实现js真正的私有属性,更多的是针对于属性操作,而这个作用更倾向于限制方法使用。
防止不必要的资源消耗
运用代理可以在一些资源使用比较大的情况下,缓冲对服务器的访问,并再可能的时候去读取缓存,而不是按照用户的要求频繁请求服务器。
有一种情况就是当用户已经成功将文件进行下载,在下载过程中,用户还在继续请求下载接口,我们可以通过判断该用户是否已请求和正在下载和是否缓存存在来决定是否再次通过get方法来获取资源。
即时撤销
可撤销的proxy实际上就是在定义代理的时候,运用了revocable方法。且运用这个方法的时候,不需要通过new来定义proxy。定义如下:
Proxy.revocable(target,handler);
举个小例子:
let target = {
name:'星星',
age:23
};
let handler = {
get(target,key){
return target[key];
}
};
let obj = Proxy.revocable(target,handler);
console.log(obj.proxy.name);//'星星'
obj.revoke();//取消代理
console.log(obj.proxy.name);//'Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked'