一、Proxy的概述
这里可以引出三个角色,分别是供应商
、代理商
、用户
。这三个角色的关系就是供应商为代理商供货,用户向代理商购买。这个列子就可以类比到这篇文章的主角Proxy(它充当代理商的角色)
Proxy
在目标对象(供应商)的外层搭建了一层拦截,外界(用户)对目标对象(供应商)的某些操作,必须通过这层拦截(Proxy)
//定义一个target对象,即供应商
let target = {
time: '2017-03-11',
name: 'net',
_r: 123
};
new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为
let monitor = new Proxy(target, handler);
let handler = {
// 此处添加拦截行为
};
对于用户而言,通过直接操作monitor
console.log('get', monitor.time);//2017-03-11
二、Proxy的handler
实际上handler
本身就是ES6
所新设计的一个对象.它的作用就是用来 自定义代理对象的各种可代理操作 。它本身一共有13中方法,每种方法都可以代理一种操作。其13种方法如下
// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.getPrototypeOf()
// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.setPrototypeOf()
// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.isExtensible()
// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.preventExtensions()
// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.getOwnPropertyDescriptor()
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
andler.defineProperty()
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()
// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()
// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()
// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()
// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.apply()
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
handler.construct()
这里我们写几个比较常用的,比方说拦截对象属性的读取,也就是对象的get
方法
let handler = {
// 拦截对象属性的读取
get(target, key) {
// 此处将key对应的value中若包含2017字符,则替换为2018
return target[key].replace('2017', '2018');
}
};
现在我们再来打印下monitor.time
console.log(monitor.time);//2018-03-11
那我们知道对象属性值是可以修改的,我们可以用过monitor.name='modify'
进行修改
monitor.name = 'modify';
console.log(monitor.name);//modify
monitor.time = '2018';
console.log(monitor.time);//2018
那如果我们需要对这个目标对象进行修改操作时限制仅部分属性值可被修改,其他属性只读不可修改呢?这里我们拦截对象设置属性的set
方法
let handler = {
// 拦截对象属性的读取
get(target, key) {
// 此处将key对应的value中若包含2017字符,则替换为2018
return target[key].replace('2017', '2018');
},
// 拦截对象设置属性
set(target, key, value) {
// 限制仅name属性可修改,其他属性只读不可修改
if (key === 'name') {
// 如果是key为name,则将需要修改的值赋值给该属性的value
return target[key] = value;
} else {
// 否则直接返回这个key原本的value
return target[key];
}
},
};
再来尝试下对目标对象的修改
monitor.name = 'modify';
console.log(monitor.name);//modify
monitor.time = '2018';
console.log(monitor.time);//2017-03-11
可以发现monitor.time
的赋值操作没有生效,而name
属性的赋值却是生效的
除此之外,还可以拦截key in object
、delete
、Object.keys
等操作
let handler = {
// 拦截对象属性的读取
get(target, key) {
// 此处将key对应的value中若包含2017字符,则替换为2018
return target[key].replace('2017', '2018');
},
// 拦截对象设置属性
set(target, key, value) {
// 限制仅name属性可修改,其他属性只读不可修改
if (key === 'name') {
// 如果是key为name,则将需要修改的值赋值给该属性的value
return target[key] = value;
} else {
// 否则直接返回这个key原本的value
return target[key];
}
},
// 拦截key in object操作 判断当前对象中是否有这个属性
has(target, key) {
// 若key值为name,则表示对象中包含这个属性;否则直接返回不包含
if (key === 'name') {
return target[key];
} else {
return false;
}
},
// 拦截delete
deleteProperty(target, key) {
// 若key值包含下划线_ 则删除该对象属性;否则不删除
if (key.indexOf('_') > -1) {
delete target[key];
return true;
} else {
return target[key];
}
},
// 拦截Object.keys,Object.getOwnPropertySymbols,Object.getOwnPropertyNames
ownKeys(target) {
// 只返回key值不是time的其他属性,相当于保护了time属性
return Object.keys(target).filter(item => item !== 'time');
}
};
测试下这些拦截是否有效
console.log('has', 'name' in monitor, 'time' in monitor);//true false
console.log('ownKeys', Object.keys(monitor));//["name", "_r"]
delete monitor.time;//key值不包含下划线,删除不成功
console.log('delete', monitor);
delete monitor._r;//key值包含下划线,删除成功
console.log('delete', monitor);
三、Reflect的使用
Reflect
的使用方法同Proxy相同,简单看一个例子🌰
{
let obj = {
time: '2017-03-11',
name: 'net',
_r: 123,
};
console.log('Reflect get', Reflect.get(obj, 'time'));//2017-03-11
Reflect.set(obj, 'name', 'modify');
console.log('Reflect set', obj);//{time: "2017-03-11", name: "modify", _r: 123}
console.log('has', Reflect.has(obj, 'name'));//true
}
四、使用场景
在开发过程中,经常会对一些数据做一些校验,比方说年龄是否大于18岁、手机号码格式是否正确等。这个时候就可以用Proxy
和Reflect
来实现和业务的解藕。
{
function validator(target, validator) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
if (target.hasOwnProperty(key)) {
let v = this._validator[key];
if (!!v(value)) {
return Reflect.set(target, key, value, proxy);
} else {
throw Error(`不能设置${key}`);
}
} else {
throw Error(`${key}不存在`);
}
}
})
}
const personValidators = {
name(val) {
// 校验name的类型是否为字符串类型
return typeof val === 'string';
},
age(val) {
// 校验age是否是数值类型,同时年龄要大于18岁
return typeof val === 'number' && val > 18;
}
};
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
return validator(this, personValidators);
}
}
let person = new Person('zhangsan', 30);
console.log(person);//Proxy {name: "zhangsan", age: 30}
// person.name = 10;//报错 Error: 不能设置name
person.name = 'lisi';
console.log(person);//Proxy {name: "lisi", age: 30}
}
网友评论