代理与反射
文章目录
一、代理基础
ECMAScript新增的代理与反射为开发者提供了拦截并项基本操作嵌入额外行为的能力。
代理是目标对象的抽象。
1.创建空代理
空代理即除了作为一个抽象的目标对象,什么也不做。
代理是使用Proxy构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺少任何一个参数都会抛出TypeError。
代码如下(示例):
const target={
id:'target'
};
const handler={};
const proxy=new Proxy(target,handler);
//id属性会访问同一个值
console.log(target.id);//target
console.log(proxy.id);//target
//给目标属性赋值会反映在两个对象上
//两个对象访问的是同一个值
target.id='bar';
console.log(target.id);//bar
console.log(proxy.id);//bar
//给代理属性赋值会反映两个对象上
//赋值会转移到目标对象上
proxy.id='foo';
console.log(target.id);//foo
console.log(proxy.id);//foo
2.定义捕获器
定义代理的主要目的是可以使用捕获器。捕获器就是在处理程序对象中定义的“基本操作的拦截器”。
只有在代理对象上执行这些操作(proxy[property]、proxy.property、Object.create(proxy)[property])才会触发捕获器。在目标对象上执行这些操作仍然会产生正常行为。
代码如下(示例):
//定义get()捕获器
const target={
foo:'bar'
};
const handler={
//捕获器在处理程序对象中以方法名为键
get(){
return 'handler override';
}
}
const proxy=new Proxy(target,handler);
console.log(target.foo);//bar
console.log(proxy.foo);//handler override
console.log(target['foo']);//bar
console.log(proxy['foo']);//handler override
console.log(Object.create(target)['foo']);//bar
console.log(Object.create(proxy)['foo']);//handler override
3.捕获器参数和反射API
所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。
代码如下(示例):
const target={
foo:'bar'
};
const handler={
get(trapTarget,property,receiver){
console.log(trapTarget==target);
console.log(property);
console.log(receiver==proxy);
}
}
const proxy=new Proxy(target,handler);
proxy.foo;
//true
//foo
//true
处理程序对象中所有可以捕获的方法都有对应的反射API方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。
代码如下(示例):
const target={
foo:'bar'
};
const handler={
get(){
return Reflict.get(...arguments);
}
};
const proxy=new Proxy(target,handler);
console.log(proxy.foo);//bar
console.log(target.foo);//bar
4.捕获器不变式
根据ECMAScript规范,每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”。
代码如下(示例):
const target={};
Object.defineProperty(target,'foo'{
consigurable:false,
writable:false,
value:'bar',
};
const handler={
get(){
return 'qux';
}
}
const proxy=new Proxy(target,handler);
console.log(proxy.foo);
//TypeError
5.可撤销代理
revocable()方法支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的。而且撤销函数(revoke())是幂等的,调用多少次的结果都一样。撤销代理之后再调用代理会抛出TypeError。
代码如下(示例):
const target={
foo:'bar'
};
const handler={
get(){
return 'itercepted';
}
};
const {proxy,revoke} = Proxy.revokable(target,handler);
console.log(proxy.foo);//itercepted
console.log(target.foo);//bar
revoke();
console.log(proxy.foo);//TypeError
6.实用反射API
反射API与对象API
在使用反射API时,要注意:
- 反射API并不限于捕获处理程序
- 大多数反射API方法在Object类型上有对应的方法
状态标记
很多反射方法返回称作“状态标记”的布尔值,表示意图执行的操作是否成功。
以下反射方法都会提供状态标记:
- Reflect.defineProperty()
- Reflect.preventExtensions()
- Reflect.setPrototypeOf()
- Reflect.set()
- Reflect.deleteProperty()
用一等函数替代操作符
以下反射方法提供只有通过操作符才能完成的操作:
- Reflect.get():可以替代对象属性访问操作符
- Reflect.set():可以替代=赋值操作符
- Reflect.has():可以替代in操作符或with()
- Reflect.deleteProperty():可替代delete操作符
- Reflect.constructor():可以替代new操作符
安全地应用函数
7.代理另外一个代理
代码如下(示例):
const target={
foo:'bar'
};
const firstProxy=new Proxy(target,{
get(){
console.log('first proxy');
return Reflect.get(...arguments);
}
});
const secondProxy=new Proxy(target,{
get(){
console.log('second proxy');
return Reflect.get(...arguments);
}
});
console.log(secondProxy.foo);
//second proxy
//first proxy
//bar
二、代理捕获器与反射方法
1.get()
get()捕获器会在获取属性值的操作中被调用。对应的反射API方法为Reflect.get()。
const myTarget={};
const proxy=new Proxy(myTarget,{
get(target,property,receiver){
console.log('get()');
return Reflect.get(...arguments);
}
}
proxy.foo;
//get()
1.返回值
返回值无限制
2.拦截的操作
proxy.property
proxy[property]
Object.create(proxy)[property]
Reflect.get(proxy,property,receiver)
3.捕获器处理程序参数
target:目标对象
property:引用的目标对象上的字符串键属性
receiver:代理对象或继承代理对象的对象
4.捕获器不变式
如果target.property不可写且不可配置,则处理程序返回的值必须与target.property匹配。
如果target.property不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined。
2.set()
get()捕获器会在设置属性值的操作中被调用。对应的反射API方法为Reflect.set()。
const myTarget={};
const proxy=new Proxy(myTarget,{
set(target,property,value,receiver){
console.log('set()');
return Reflect.set(...arguments);
}
}
proxy.foo='bar';
//set()
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:接收最初赋值的对象
4.捕获器不变式
如果target.property不可写且不可配置,则不能修改目标属性的值。
如果target.property不可配置且[[Set]]特性为undefined,则不能修改目标属性的值。
在严格模式下,处理过程返回false会抛出TypeError。
3.has()
has()捕获器会在in操作符中被调用。对应的反射API方法为Reflect.has()。
const myTarget={};
const proxy=new Proxy(myTarget,{
set(target,property){
console.log('has()');
return Reflect.has(...arguments);
}
}
'foo' in proxy
//has()
1.返回值
has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。
2.拦截的操作
property in proxy
property in Object.create(proxy)
with(proxy) {(property);}
Reflect.has(proxy,property)
3.捕获器处理程序参数
target:目标对象
property:引用的目标对象上的字符串键属性
4.捕获器不变式
如果target.property存在且不可配置,则处理程序必须返回true。
如果target.property存在且目标对象不可扩展,则处理程序必须返回true。
4.defineProperty()
defineProperty()捕获器会在**Object.defineProperty()**中被调用。对应的反射API方法为Reflect.defineProperty()。
const myTarget={};
const proxy=new Proxy(myTarget,{
defineProperty(target,property,descriptor){
console.log('defineProperty()');
return Reflect.defineProperty(...arguments);
}
}
Object.defineProperty(proxy,'foo',{value:'bar'});
//defineProperty()
1.返回值
defineProperty()必须返回布尔值,表示属性是否定义成功。返回非布尔值会被转型为布尔值。
2.拦截的操作
Object.defineProperty(proxy,property,descriptor)
Reflect.defineProperty(proxy,property,descriptor)
3.捕获器处理程序参数
target:目标对象
property:引用的目标对象上的字符串键属性
descriptor:包含可选的enumerable、configurable、writable、value、get和set定义的对象。
4.捕获器不变式
如果目标对象不可扩展,则无法定义属性。
如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。
如果目标对象有一个不可配置的属性,则不能添加同名的不可配置属性。
5.getOwnPropertyDescriptor()
getOwnPropertyDescriptor()捕获器会在**Object.getOwnPropertyDescriptor()**中被调用。对应的反射API方法为Reflect.getOwnPropertyDescriptor()。
const myTarget={};
const proxy=new Proxy(myTarget,{
getOwnPropertyDescriptor(target,property){
console.log('getOwnPropertyDescriptor()');
return Reflect.getOwnPropertyDescriptor(...arguments);
}
}
Object.getOwnPropertyDescriptor(proxy,'foo');
//getOwnPropertyDescriptor()
1.返回值
getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回undefined。
2.拦截的操作
Object.getOwnPropertyDescriptor(proxy,property)
Reflect.getOwnPropertyDescriptor(proxy,property)
3.捕获器处理程序参数
target:目标对象
property:引用的目标对象上的字符串键属性
4.捕获器不变式
如果自有的target.property存在且不可配置,则处理程序必须返回一个表示该属性存在的对象。
如果自有的target.property存在且可配置,则处理程序必须返回该属性可配置的对象。
如果自有的target.property存在且target不可扩展,则处理程序必须返回一个表示该属性存在的对象。
如果target.property不存在且target不可扩展,则处理程序必须返回undefined表示该属性存在。
如果target.property不存在,则处理程序不能返回该属性可配置的对象。
6.deleteProperty()
deleteProperty()捕获器会在delete操作符中被调用。对应的反射API方法为Reflect.deleteProperty()。
const myTarget={};
const proxy=new Proxy(myTarget,{
deleteProperty(target,property){
console.log('deleteProperty()');
return Reflect.deleteProperty(...arguments);
}
}
delete proxy.foo
//deleteProperty()
1.返回值
deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。
2.拦截的操作
delete proxy.property
delete proxy[property]
deleteProperty(proxy,property)
3.捕获器处理程序参数
target:目标对象
property:引用的目标对象上的字符串键属性
4.捕获器不变式
如果自有的target.property存在且不可配置,则处理程序不能删除这个程序。
7.ownKeys()
ownKeys()捕获器会在**Object.keys()**及类似方法中被调用。对应的反射API方法为Reflect.ownKeys()。
const myTarget={};
const proxy=new Proxy(myTarget,{
ownKeys(target){
console.log('ownKeys()');
return Reflect.ownKeys(...arguments);
}
}
Object.keys(proxy);
//ownKeys()
1.返回值
ownKeys()必须返回包含字符串或符合的可枚举对象。
2.拦截的操作
Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keysproxy)
Object.ownkeys(proxy)
3.捕获器处理程序参数
target:目标对象
4.捕获器不变式
返回的可枚举对象必须包含target的所有不可配置的自有属性。
如果target不可扩展,则返回可枚举对象必须准确地包含自有属性键。
8.getPrototypeOf()
getPrototypeOf()捕获器会在**Object.getPrototypeOf()**中被调用。对应的反射API方法为Reflect.getPrototypeOf()。
const myTarget={};
const proxy=new Proxy(myTarget,{
getPrototypeOf(target){
console.log('getPrototypeOf()');
return Reflect.getPrototypeOf(...arguments);
}
}
Object.getPrototypeOf(proxy);
//getPrototypeOf()
1.返回值
getPrototypeOf()必须返回对象或null。
2.拦截的操作
Object.getPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
proxy.__proto__
proxy instanceof Object
3.捕获器处理程序参数
target:目标对象
4.捕获器不变式
如果target不可扩展,则Object.getPrototypeOf(proxy)唯一有效的返回值就是Object.getPrototypeOf(target)的返回值。
9.setPrototypeOf()
setPrototypeOf()捕获器会在**Object.setPrototypeOf()**中被调用。对应的反射API方法为Reflect.setPrototypeOf()。
const myTarget={};
const proxy=new Proxy(myTarget,{
setPrototypeOf(target,prototype){
console.log('setPrototypeOf()');
return Reflect.setPrototypeOf(...arguments);
}
}
Object.setPrototypeOf(proxy);
//setPrototypeOf()
1.返回值
getPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。
2.拦截的操作
Object.setPrototypeOf(proxy)
Reflect.setPrototypeOf(proxy)
proxy.__proto__
proxy instanceof Object
3.捕获器处理程序参数
target:目标对象
property:target的替代原型,如果是顶级原型则为null
4.捕获器不变式
如果target不可扩展,则唯一有效的参数就是Object.getPrototypeOf(target)的返回值。
10.isExtensible()
isExtensible()捕获器会在**Object.isExtensible()**中被调用。对应的反射API方法为Reflect.isExtensible()。
const myTarget={};
const proxy=new Proxy(myTarget,{
isExtensible(target){
console.log('isExtensible()');
return Reflect.isExtensible(...arguments);
}
}
Object.isExtensible(proxy);
//isExtensible()
1.返回值
isExtensible()必须返回布尔值,表示target是否可扩展。返回非布尔值会被转型为布尔值。
2.拦截的操作
Object.isExtensible(proxy)
Reflect.isExtensible(proxy)
3.捕获器处理程序参数
target:目标对象
4.捕获器不变式
如果target可扩展,则处理程序必须返回true。
如果target不可扩展,则处理程序必须返回false。
11.preventExtensions()
preventExtensions()捕获器会在**Object.preventExtensions()**中被调用。对应的反射API方法为Reflect.preventExtensions()。
const myTarget={};
const proxy=new Proxy(myTarget,{
preventExtensions(target){
console.log('preventExtensions()');
return Reflect.preventExtensions(...arguments);
}
}
Object.preventExtensions(proxy);
//preventExtensions()
1.返回值
preventExtensions()必须返回布尔值,表示target是否已经不可扩展。返回非布尔值会被转型为布尔值。
2.拦截的操作
Object.preventExtensions(proxy)
Reflect.preventExtensions(proxy)
3.捕获器处理程序参数
target:目标对象
4.捕获器不变式
如果Object.preventExtensions(proxy)是false,则处理程序必须返回true。
12.apply()
apply()捕获器会在调用函数时中被调用。对应的反射API方法为Reflect.apply()。
const myTarget={};
const proxy=new Proxy(myTarget,{
apply(target,thisArg,...argumentsList){
console.log('apply()');
return Reflect.apply(...arguments);
}
}
(proxy);
//apply()
1.返回值
返回值无限制。
2.拦截的操作
proxy(…argumentsList)
Function.prototype.apply(thisArg,argumentsList)
Function.prototype.call(thisArg,…argumentsList)
Reflect.prototype.apply(thisArg,argumentsList)
3.捕获器处理程序参数
target:目标对象
thisArg:调用函数时的this参数
argumentsList:调用函数时的参数列表
4.捕获器不变式
target必须是一个函数对象。
13.construct()
construct()捕获器会在new操作符中被调用。对应的反射API方法为Reflect.construct()。
const myTarget={};
const proxy=new Proxy(myTarget,{
construct(target,argumentsList,newTarget){
console.log('construct()');
return Reflect.construct(...arguments);
}
}
new proxy;
//construct()
1.返回值
construct()必须返回一个对象。
2.拦截的操作
new proxy(…argumentsList)
Reflect.construct(target,argumentsList,newTarget)
3.捕获器处理程序参数
target:目标对象
argumentsList:调用函数时的参数列表
newTarget:最初被调用的构造函数
4.捕获器不变式
target必须可以用作构造函数。
三、代理模式
1.跟踪属性访问
通过捕获get、set和has等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放在应用中,可以监控这个对象何时在何处被访问过:
代码如下(示例):
const user={
name:'Jake'
};
const proxy=new Proxy(user,{
get(target,property,receiver){
console.log(`Getting ${property}`);
return Reflect.get(...arguments);
},
set(target,property,value,receiver){
console.log(`Setting ${property}=${value}`);
return Reflect.set(...arguments);
}
});
proxy.name; //Getting name
proxy.age=27; //Setting age=27
2.隐藏属性
3.属性验证
因为所有赋值操作都会触发set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值。
4.函数与构造函数参数验证
5.数据绑定与可观察对象
总结
代理是ECMAScript6新增的令人兴奋和动态十足的新特性。尽管不支持向后兼容,但它开辟了一片前所未有的JavaScript元编程及抽象的新天地。
从宏观上看,代理是真实JAVaScript对象的透明抽象层。代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分JavaScript的基本操作和方法。在这个跟捕获器处理程序中,可以修改任何基本操作的行为,当然前期是遵从捕获器不变式。
与代理如影随形的反射API,则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射API看作一套基本操作,这些操作是绝大部分JavaScript对象API的基础。
代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象。