Proxy

概述

Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。
Proxy可以理解为,在目标对象之前架设一层"拦截",外界对该对象的访问,都必须先通过这层拦截 ,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy的原意是代理,用在这里表示它来”代理“某些操作,可以译为”代理器“。

var obj=new Proxy({},{
	get:function(target,propKey,receiver){
		console.log(`getting ${propKey}!`);
		return Reflect.get(target,propKey,receiver);
	},
	set:function(target,propKey,value,receiver){
		console.log(`setting ${propKey}!`);
		return Reflect.set(target,propKey,value,receiver);
	}
})

上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写他的属性,就会得到下面的结果。

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2

上面代码说明,Proxy实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义。
ES6原生提供了Proxy构造函数,用来生成Proxy实例。

var proxy=new Proxy(target,handler);

Proxy对象的所有用法,都是上面这种形式,不同的是handler参数的写法。其中,new Proxy()表示生成一个proxy 实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
下面是另一个拦截读取属性行为的例子。

var proxy=new Proxy({},{
	get:function(target,propKey){
		return 35;
	}
});

proxy.time  //35
proxy.name  //35
proxy.title //35

上面的代码中,作为构造函数,Proxy接收两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。

注意:要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
如果handler没有设置任何拦截,那就等同于直接通向原对象。

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target。

一个技巧就是将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用。

var object = { proxy: new Proxy(target, handler) };

Proxy实例也可以作为其他对象的原型对象。

var proxy=new Proxy({},{
	get:function(target,propKey){
		return 35;
	}
})

let obj=Object.create(proxy);
obj.time //35

上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链 ,会在proxy对象上读取该属性,导致被拦截。

同一个拦截器函数,可以设置拦截多个操作。

Proxy实例的方法

  • get()
    get方法用于拦截某个属性的读取操作,可以接受三个参数,依次是目标对象、属性名和proxy实例本身(严格来说,是操作行为所针对的对象),其中最后一个参数可选。

get方法的用法,上文已经有一个实例,下面是一个拦截读取操作的例子。

var person={
	name:"张三"
};
var proxy=new Proxy(person,{
	get:function(target,propKey){
		if(propKey in target){
			return target[propKey];
		}else{
			throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.")
		}
	}
});

proxy.name //“张三”
proxy.age  //“抛出一个错误”

上面的代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有拦截,访问不存在的属性,只会返回undefined。

get方法可以继承。

let proto=new Proxy({},{
	get(target,propertyKey,receiver){
		console.log('GET'+propertyKey);
		return target[propertyKey];
	}
})

let obj=Object.create(proto);
obj.foo//"GET foo"

上面的代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。

  • set()
    set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和Proxy实例本身,其中最后一个参数可选。

假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用proxy保证age的属性值符合要求。

let validator={
	set:function(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');
			}
		}
		obj[prop]=value;
		return true;
	}
};

let person=new Proxy({},validator);

person.age //100
person.age='young' //报错
person.age=300 //报错

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生改变的时候,会自动更新DOM。

有时,我们会在对象上设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合get和set方法,就可以做到防止这些内部属性被外部读写。

const 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`);
	}
}

const target={};
const proxy=new Proxy(target,handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property

上面的代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。

  • apply()
    apply方法拦截函数的调用、call和apply操作。
    apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
var handler={
	apply(target,ctx,args){
		return Reflect.apply(...arguments);
	}
}

下面是一个例子。

var target=function(){
	return 'I am the target';
}
var handler={
	apply:function(){
		return 'I am the proxy';
	}
};
var p=new Proxy(target,handler);
p();
//I am the proxy;

上面的代码中,变量p是Proxy的实例,当他作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。

你可能感兴趣的:(ES6,javascript)