第九章 代理和反射
3个要点
1、代理基础
2、代码捕获器与反射方法
3、代理模式
9.1 代理基础
代理是目标对象的抽象,类似于C++中的指针,因为它可以用作目标对象的替身,但又完全独立于目标对象。
9.1.1 创建空代理
最简单的代理就是空代理,即抽象一个目标对象,什么也不做。默认情况下,在代理对象上执行的素有操作都会五拽的传播到目标对象上。因此,在任何可以使用目标对象的地方,都可以通过同样的方式来使用与之关联的代理对象。
代理是通过Proxy构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象,缺少一个都会抛出TypeError的异常
sample 1 改变对象,指针也改变
const target={
id:'thisISID'
};
const handler={};
const proxy=new Proxy(target,handler);
//id属性会访问同一个值
console.log(target.id); //返回 'thisISID'
console.log(proxy.id); //返回 'thisISID'
//改变目标对象的属性,指针也会改变
target.id="我要变了";
console.log(target.id); //返回 '我要变了'
console.log(proxy.id); //返回 '我要变了'
hasOwnProperty()
console.log(target.hasOwnProperty('id')); //返回true
conosle.log(proxy.hasOwnProperty('id')); //返回true
不能用 instanceof
console.log(target instanceof Proxy); //TypeError
console.log(proxy instacneof Proxy); //TypeError
9.1.2 定义捕获器
使用代理的目的就是可以定义捕获器(trap)捕获器就是在处理程序对象中定义的“基本操作的拦截器”。
sample
const target={
foo:"bar"
};
const handler={
//捕获器在处理程序对象中以方法名键
get(){
return '不允许读';
}
};
const proxy=new Proxy(target,handler);
console.log(proxy.foo); //被get 拦截,不允许读
console.log(target.foo); //返回 bar 在目标对象上不受影响
9.1.3 捕获器参数和反射API
所有捕获器都可以访问相应的参数,基于这些参数可以被重建捕获方法的原始行为。get()捕获器会接收目标对象,查询的属性和代理对象三个参数
const target={
name:"JackMa",
age:59,
job:"福报厂创始人"
};
const handler={
get(trapTarget,property,receiver){
console.log(trapTarget===target);
console.log(property);
console.log(receiver===proxy);
}
};
const proxy=new Proxy(target,handler);
proxy.name; //返回true name true
通过上面的捕获器就可以重建被捕获方法的原始行为
const target={
name:"JackMa",
age:59,
job:"福报厂创始人"
};
const handler={
get(trapTarget,property,receiver){
console.log(trapTarget===target);
console.log(trapTarget[property]);
console.log(receiver==proxy);
return trapTarget[property];
}
}
console.log(proxy.name); //返回 true JackMa true JackMa
conosle.log(target.name); //返回 JackMa
所有捕获器都可以基于自己的参数重建原始操作,但并非所有的捕获器行为都像get()那么简单。因此通过手动写代码时不现实的。实际上可以通过调用全局Reflect对象上(封装了原始行为)的同名方法来轻松重建
处理程序对象中所有可以被捕获的方法都有相应的反射(Reflect) API方法。这些方法与捕获器的方法加油相同的名称和函数签名,而且也具有与被拦截的方法相同的行为。
反射的例子
let person ={
name:"JackMA",
age:59,
job:'福报厂CEO'
};
let handler={
get(){
return Reflect.get(...arguments);
}
};
const proxy=new Proxy(target,handler);
console.log(proxy.name); //返回JackMa
console.log(person.name); //返回JackMa
9.1.4 捕获器不变式
使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。根据ECMAScipt规范,每个捕获的方法都知道目标对象的上下文、捕获函数签名。而捕获处理程序的行为必须遵守”捕获器不变式(trap invariant)”
比如,如果一个对象又一个补课配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,就抛出TypeError
let person={};
Object.defineProperty(person,'name',{
configurable:false,
writable:false,
value:"JackMa"
});
let handler={
get(){
return "XXX";
}
};
let handler2={
get(){
return "JackMa";
}
}
let proxy=new Proxy(person,handler);
console.log(proxy.name); //返回TypeError
let proxy2=new Proxy(person,handler2);
console.log(proxy2.name); //返回 JackMa
9.1.5 可撤销代理 revoke()函数
有时候可能需要终端代理对象与目标对象之间的联系。使用new Proxy()创建的普通代理来说,这种联系会在代理对象的生命周期内一直存在。
Proxy也暴露了revocable()方法,这个方法支持撤销代理与目标对象的关系。
revoke()时幂等的,调用多少次的结果都一样。撤销代理之后再调用代理会抛出TypeError
const person={
name:'JackMa'
};
const handler={
get(){
return 'this is a ret';
}
};
const {proxy,revoke}=Proxy.revocable(person,handler);
cosole.log(proxy.name); //返回 this is a ret
cosole.log(person.name); //返回 JackMa
revoke();
console.log(proxy.name); //返回TypeError
9.1.6 实用反射API
某些情况下应该优先使用反射API ,这是由一定的理由的。
1、反射API与对象API
在使用反射API时,要记住:
(1)、反射API并不限于捕获处理程度。
(2)、大多数反射API方法在Object类型上有对应的方法。
通常,Object上的方法适用于通用程序,而反射方法适用于细粒度的对象控件与操作。
2、状态标记
很多反射方法返回称作”状态标记”的布尔值,表示意图执行的操作是否成功。有时候,状态标记比那些返回修改后的对象或者抛出错误(取决于方法)的反射API方法更有用。
例如,可以使用反射API对下面的代码进行重构
//初始化代码
const o={};
try{
Object.defineProperty(o,'name','JackMa');
console.log('sucess');
}catch(e){
console.log('failure');
}
//这里返回 failure
在定义新属性时如果发生问题,Reflect.defineProperty()会返回false,而不是抛出错误。因此使用这个反射方法可以重构上面代码
//重构后的代码
let person={};
if(Reflect.defineProperty(person,'name','JackMa')){
console.log('sucess!');
}else{
console.log('failure');
}
//返回 sucess
console.log(person.name); //返回JackMa
以下反射方法都会提供状态标记:
1、类方法 Reflect.preventExtensions(target)
此方法是阻止新属性添加到对象上去
let person={};
Reflect.preventExtension(person);
if(Reflect.defineProperty(person,'name','JackMa')){
console.log('Sucess!');
}else{
console.log('failure!');
}
//返回 failure!
2、类方法 Reflect.deleteProperty(target,propertyKey);
9.1.7 代理另一个代理
代理可以拦截反射API的操作,而着以为着完全可以创建一个代理,通过它去代理另一个代理,这样可以在一个目标上构建多个拦截网。
let person={
name:"JackMa",
age:59
};
let firstProxy=new Proxy(target,{
get(){
console.log('first Proxy');
return Reflect.get(...arguments);
}
});
const secondProxy=new Proxy(firstProxy,{
get(){
console.log('第二次代理');
return Reflect.get(...arguments);
}
})
console.log(secondProxy.name);
//返回 第二次代理 first Proxy JackMa
9.1.8 代理的问题与不足
1、代理中this指向问题
this 指向调用这个方法的对象
let person={
thisValEqu(){
return this===proxy;
}
}
const proxy=new Proxy(person,{});
console.log(person.thisValEqu()); //返回false
console.log(person.thisValEqu()); //返回true
2、代理与内部槽位
代理和内置引用类型,比如Array的实例可以很好的协同。但有些ECMAScript内置类型可能会依赖代理无法控制的机制,结果导致代理上的某些错误
let timi=new Date();
const proxy=new Proxy(timi,{});
console.log(proxy istance of Date); //返回true
proxy.getDate(); //TypeError
9.2 代理捕获器与反射方法
代理可以捕获13种不同的操作方法,这些操作有各自不同的反射API方法。参数,关联ECMAScript操作和不变式
9.2.1 get()
get()捕获器会在获取属性值的操作中被调用。对应的反射API方法为Reflect.get()
let person={
name:"JackMa",
}
let proxy=new Proxy(person,{
get(tar,prop,rec){
console.log(tar);
console.log(prop);
console.log(receiver);
console.log(tar[prop]);
return Reflect.get(...arguments);
}
});
proxy.name; //返回 Object name proxy JackMa
1、返回值
返回值无限制
2、拦截的操作
1、proxy.property
2、proxy[property]
3、Object.create(proxy)[property]
4、Reflect.GET(proxy,property,receiver)
3、捕获器处理程序参数
target:目标对象
property:引用的目标对象上的属性
receiver:代理对象或者继承代理对象的对象
4、捕获器不变式
如果target.property不可写且不可配置,则处理程序放回的值必须与target.property匹配
9.2.2 set()
set()捕获器会在设置属性值的操作中被调用。对应的反射API方法为Reflect.set()
let person={
name="Timi"
}
let proxy=new Proxy(person,{
set(target,property,value,receiver){
console.log('set()');
return Reflect.set(...arguments);
}
});
proxy.name="JackMa"; //返回 set()
console.log(proxy.name); //返回 JackMa
1、返回值
返回true表示成功;返回false表示失败,严格模式下会抛出TypeError
2、拦截操作
proxy.property=value
proxy[property]=value
Object.create(proxy)[property]=value
Reflect.set(proxy,property,value,receiver);
3、捕获器处理程序参数
target 目标对象 property 对象上的属性
value 要赋值的值 receiver 接收最初赋值的对象
9.2.3 has()
has()捕获器会在in操作父中被调用。对应的反射API方法为Reflect.has()
let person={
name:'JackMa',
}
let proxy=new Proxy(person,{
has(target,property){
console.log('Has');
return Reflect.has(...arguments);
}
});
console.log('JackMa' in proxy); //返回false
console.log('name' in proxy); //返回true
9.2.4defineProperty()
defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射API方法为Reflect.defineProperty()
let person={
};
const proxy=new Proxy(person,{
defineProperty(target,property,descriptor){
console.log('defineProperty()');
return Reflect.defineProperty(...arguments)
}
});
Object.defineProperty(proxy,'name',{value:'JackMa'}); //返回true
console.log(proxy.name); //返回Jack Ma
9.2.5getOwnPropertyDescriptor()
9.3代理模式(暂时略过)
使用代理可以在代码中实现一些有用的编程模式
9.3.1 跟踪属性访问
通过捕获get、set和has等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象合适再何处被访问