proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种"元编程",即对编程语言进行编程。
proxy可以理解为在目标对象前假设一个“拦截”层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由他来“代理”某些操作,可以翻译为“代理器”.
proxy对象的所有用法都是上面这种形式,不同的只是handler参数的写法,其中new Peoxy()表示生成一个proxy实例,target参数表示要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
举一个例子:如下
{
//举个例子
let proxy = new Proxy({},{
get:function(target,property){
return 35;
}
});
console.log(proxy.name);
console.log(proxy.age);
//proxy就收两个参数,第一个参数是所要代理的目标对象,第二个参数是一个配置对象,需要提供一个相应的处理函数。
//get方法的两个参数分别是目标对象和索要访问的属性。可以看到拦截函数总是返回35,所以访问任何属性都是得到35
}
{
//如果handler没有设置任何拦截,那就等同于直接通向源对象
let target = {};
let handler = {};
let proxy = new Proxy(target,handler);
proxy.a = 'b';
console.log(target.a); //b
}
1.proxy实例方法 --> get()
get方法用于拦截某个属性的读取操作。前面已经有一个例子,下面是另一个拦截读取操作的例子。
let person = {
name:'kjh'
}
let proxy = new Proxy(person,{
get(target,property){
if(property in target){
return target[property]
}else{
throw new ReferenceError('Property:'+ property + 'dose not exist!');
}
}
})
console.log(proxy.name); //kjh
console.log(proxy.age); //age dose not exist!
上面的代码表示,如果访问目标对象不存在的属性,会抛出一个错误,但是如果没有这个拦截函数,访问不存在的属性,指挥返回undefined
如果一个属性不可配置或者不可写,那么属性就不能被代理,通过proxy对象访问该属性就会报错。
{
let target = {
name:'123'
}
Object.defineProperty(target,'name',{
writable:false,
configurable:false
})
let proxy = new Proxy(target,{
get(target,property){
return 'abc';
}
})
console.log( proxy.name);
//property 'name' is a read-only and non-configurable data
}
2.proxy实例方法 --> set()
set方法用于拦截某个属性的赋值操作
{
//加入person对象有一个age属性,该属性应该是一个不大于200的证书,那么proxy可以对象保证age的属性值符合要求
let validator = {
set(obj, prop, value){
if(prop === 'age'){
if(!Number.isInteger(value)){
throw new TypeError('The age is not an integer')
}
if(value > 200){
throw new RangeError('The age seems invalid');
}
}
//对于age以外的属性,直接保存
obj[prop] = value;
}
}
let person = new Proxy({},validator);
console.log(person.age = 100);
console.log(person.age = 201);//The age seems invalid
console.log(person.age = 'age'); //The age is not an integer
//上述代码中,由于设置了存值函数set,任何不符合要求的age属性赋值都会抛出一个错误,这是数据验证的一种实现方式.
//利用set方法还可以实现数据绑定,即每当对象发生变化是,会自动更新DOM.
}
{
//防止内部属性被外部读写
let handler = {
get(target ,key){
invariant(key, 'get');
return target[key];
},
set(target, key){
invariant(key,'set');
target[key] = value;
return true;
}
}
function invariant(key, action){
if(key[0] === '_'){
throw new Error(`Invalid attempt to ${action} private "${key}" property` );
}
}
let target = {};
let proxy = new Proxy(target,handler);
console.log(proxy._prop); //Invalid attempt to get private "_prop" property
}
3.proxy实例方法 --> apply()
apply 方法拦截函数的调用,call和apply操作
apply 方法可以接受三个参数,分别是目标对象,目标对象的上下文对象(this),和目标对象的参数数组
{
//举个例子
let target = function(){
return 'I am the target';
}
let handler = {
apply(){
return 'I am the proxy'
}
}
let p = new Proxy(target,handler);
console.log(p()); //I am the proxy
//上面的代码中,变量p是proxy的实例,作为函数调用时就会被apply方法拦截,返回其对应的字符串.
}
4.proxy实例方法 --> has()
has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效.
{
// 下面使用has方法隐藏了某些属性,使其不被in运算符发现
let handler = {
has(target, key){
if(key[0] === '_'){
return false;
}
return key in target;
}
}
var target = {
_prop:'foo',
name:'123'
}
let proxy = new Proxy(target, handler);
console.log('_prop' in proxy);
//false
}
has()方法只拦截in运算符,但是不拦截for...in..运算符
{
let stu1 = {
name:'张三',
score:56
}
let stu2 = {
name:'李四',
score:100
}
let handler = {
has(target, prop){
if(prop === 'score' && target[prop] < 60){
console.log(`${target.name}不及格`);
return false
}
return prop in target;
}
}
let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);
// console.log('score' in oproxy1);
//张三不及格
//false
// console.log('score' in oproxy2);
//true
for(let a in oproxy1){
console.log(oproxy1[a]);
}
//张三
//56
for(let b in oproxy2){
console.log(oproxy2[b]);
}
//李四
//100
// 上面的代码中,has拦截只对in循环生效,对for...in玄幻不生效,导致不符合要求的属性没有被排除在for...of循环外面
}
5.proxy实例方法 --> construct()
construct方法用于拦截new命令,
可以接受两个参数,target目标对象和args构建函数的参数对象
{
let p = new Proxy(function (){},{
construct(target, args){
console.log('called: '+ args.join(', '));
return {
value:args[0] * 10
}
}
})
console.log((new p(1)).value)
//called: 1
//10
}
注意返回值必须是一个对象,否则会报错
6.proxy实例方法 --> deleteProperty()
deleteProperty()用于拦截delete操作,返回false,这个属性就无法被删除
{
let handler = {
deleteProperty(target, key){
invariant(key, 'delete');
return true;
}
}
function invariant(key ,action){
if(key[0] === '_'){
throw new Error(`Invalid attempt to ${action} private "${key}" property` );
}
}
let target = {_name:"kjh"};
let proxy = new Proxy(target,handler);
console.log(delete proxy._name);
//nvalid attempt to delete private "_name" property
//上面代码,deletePropertyF方法拦截了delete操作符,删除第一个字符为下划线的属性会报错.
}
proxy 剩余实例方法和常用的例子将在下一篇介绍。