1. 代理与反射
- 内容
- 代理基础
- 代码捕获器与反射方法
- 代理模式
1.1.1 创建空代理
- 最简单的代理是空代理,即除了作为一个抽象的目标对象,什么也不做。默认情况下,在代理对象上执行的所有操作都会无障碍地传播到目标对象。
- 代理是使用Proxy构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺少其中任何一个参数都会抛出TypeError。
const target = {
id: 'target'
}
const handler = {}
const proxy = new Proxy(target,handler)
console.log(target.id);
console.log(proxy.id);
target.id = 'foo'
console.log(target.id);
console.log(proxy.id);
proxy.id = 'bar'
console.log(target.id);
console.log(proxy.id);
console.log(target.hasOwnProperty('id'));
console.log(proxy.hasOwnProperty("id"));
console.log(target instanceof Proxy);
console.log(proxy instanceof Proxy);
console.log(target === proxy);
1.1.2 定义捕获器
- 使用代理的主要目的是可以定义捕获器。捕获器就是在处理程序中定义的 "基本操作的拦截器"。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。
const target = {
id: 'foo'
}
const handler = {
get(){
return 'handler override'
}
}
const proxy = new Proxy(target,handler)
console.log(target.id);
console.log(proxy.id);
console.log(target['id']);
console.log(proxy['id']);
console.log(Object.create(target)['id']);
console.log(Object.create(proxy)['id']);
1.1.3 捕获器参数和反射API
- 所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。比如get()捕获器会接收到目标对象、要查询的属性、代理对象三个参数。
const target = {
id: 'foo'
}
const handler = {
get(trapTarget,property,trapProxy){
console.log(trapTarget == target);
console.log(trapProxy == proxy);
console.log(property);
}
}
var proxy = new Proxy(target,handler)
proxy.id
const handler2 = {
get(trapTarget,property,trapProxy){
return trapTarget[property]
}
}
var proxy2 = new Proxy(target,handler2)
console.log(proxy2.id);
console.log(target.id);
- 通过调用全局Reflect对象上的同名方法重建原始操作。处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。
const target = {
id: 'foo'
}
const handler = {
get(){
return Reflect.get(...arguments)
}
}
var proxy = new Proxy(target,handler)
console.log(proxy.id);
console.log(target.id);
const handler2 = {
get(){
return Reflect.get
}
}
- 实际上,如果真想创建一个可以捕获所有方法,然后将每个方法转发给对应反射API的空代理。那么甚至不需要定义处理程序对象。
const target = {
id: 'foo'
}
var proxy = new Proxy(target,Reflect)
console.log(target.id);
console.log(proxy.id);
- 反射API对返回值进行修饰
const target = {
foo: 'bar',
baz: 'zxc'
}
const handler = {
get(trapTarget,property,trapProxy){
let deraction = ''
if(property == 'foo'){
deraction = '!!!'
}
return Reflect.get(...arguments) + deraction
}
}
var proxy = new Proxy(target,handler)
console.log(proxy.foo);
console.log(target.foo);
1.1.4 捕获器不变式
- 使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。比如,如果目标对象有一个不可配置属性且不可写的属性,那么在捕获器返回一个与该属性不同的值时,就会报错。
const target = {
id: '123'
}
Object.defineProperty(target,'name',{
configurable: false,
enumerable: false,
writable: false,
value: 'Nicholas'
})
const handler = {
get(){
return 'qux'
}
}
var proxy = new Proxy(target,handler)
console.log(proxy.name);
1.1.5 可撤销代理
- 有时候需要中断代理对象与目标对象之间的联系。对于使用new Proxy()创建的普通代理来说,这种联系会在代理对象的生命周期内一直存在。
- proxy暴露了revocable()方法,这个方法支持撤销代理对象和目标对象之间的关联。撤销代理的操作是不可逆的。撤销代理之后再调用代理会抛出错误。
const target = {
id: 'foo'
}
const handler = {
get(){
return 'intercepted'
}
}
const { proxy, revoke } = Proxy.revocable(target, handler)
console.log(proxy.id);
console.log(target.id);
revoke()
console.log(proxy.id);
1.1.6 实用反射API
- 反射API与对象API
- 反射API并不限于捕获处理函数
- 大多数反射API方法在Object类型上有对应的方法。
- 通常,Object上的方法适用于通用程序,而反射方法适用于细粒度的对象控制和操作。
- 状态标记
- 很多反射方法返回称作“状态标记”的布尔值。表示意图执行的操作是否成功。
const target = {
id: 'foo'
}
try {
Object.defineProperty(target,'name',{
value: 'Nicholas'
})
console.log('success...');
} catch (error) {
console.log('fail....');
}
const o = {}
if(Reflect.defineProperty(o,'name','Nicholas')){
console.log('success...');
}else {
console.log('fail...');
}
1.1.7 代理另一个代理
- 代理可以拦截反射API的操作,而这意味着完全可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网
const target = {
id: "foo",
};
const firstProxy = new Proxy(target, {
get() {
console.log("first proxy");
return Reflect.get(...arguments);
},
});
const secondProxy = new Proxy(firstProxy, {
get() {
console.log("second proxy");
return Reflect.get(...arguments);
},
});
console.log(secondProxy.id);
1.2 代理捕获器与反射方法
- 代理可以捕获13种不同的基本操作。这些操作有各自不同的反射API方法、参数、关联ECMAScript操作和不变式。
- 对于在代理对象上执行的任何一种操作,只会有一个捕获处理程序被调用。
1.2.1 get()
- get()捕获器会在获取属性值的操作中被调用。对应反射API的方法为Reflect.get()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
get(target,prototype,proxy){
console.log('get()');
return Reflect.get(...arguments)
}
})
proxy.foo
- set() 捕获器会在设置属性值的操作中被调用。对应反射API的方法为Reflect.set()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
set(target,property,receiver){
console.log('set()');
return Reflect.set(...arguments)
}
})
proxy.id = 'bar'
- has() 捕获器会在in操作符中被调用,对应反射API的方法为Reflect.has()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
has(target,property,receiver){
console.log('has()');
return Reflect.has(...arguments)
}
})
'foo' in proxy
- defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射API方法为Reflect.defineProperty()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
defineProperty(target,property,descriptor){
console.log('defineProperty...');
return Reflect.defineProperty(...arguments)
}
})
Object.defineProperty(proxy,'name',{value: 'Nicholas'})
proxy.name
- getOwnPropertyDescriptor()捕获器会在Object.getOwnPropertyDescriptor()中被调用。对应反射API的方法为Reflect.getOwnPropertyDescriptor()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
getOwnPropertyDescriptor(target,property){
console.log('getOwnPropertyDescriptor...');
return Reflect.getOwnPropertyDescriptor(...arguments)
}
})
Object.getOwnPropertyDescriptor(proxy,'name')
proxy.name
- deleteProperty()捕获器会在delete操作符中被调用,对应的反射API方法为Reflect.deleteProperty()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
deleteProperty(target,property){
console.log('deleteProperty...');
return Reflect.deleteProperty(...arguments)
}
})
delete proxy.name
- ownKeys()捕获器会在Object.keys()及类似的方法中被调用。对应的反射API方法为Reflect.ownKeys()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
ownKeys(target,property){
console.log('ownKeys...');
return Reflect.ownKeys(...arguments)
}
})
Object.keys(proxy)
- getPrototypeOf()捕获器会在Object.getPrototypeOf()中被调用。对应的反射API方法为Reflect.getPrototypeOf()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
getPrototypeOf(target){
console.log('getPrototypeOf()...');
return Reflect.getPrototypeOf(...arguments)
}
})
Object.getPrototypeOf(proxy)
- setPrototypeOf()捕获器会在Object.setPrototypeOf()中被调用,对应的反射API方法为Reflect.getPrototypeOf()
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
setPrototypeOf(target,property){
console.log('setPrototypeOf()...');
return Reflect.setPrototypeOf(...arguments)
}
})
Object.setPrototypeOf(proxy,Object)
- isExtensible()捕获器会在Object.isExtensible()中被调用。对应的反射API方法为Reflect.isExtensible()
- 使用这个方法可以确定对象是否为可篡改,如果可篡改,则返回true,相反返回false。
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
isExtensible(target){
console.log('isExtensible...');
return Reflect.isExtensible(...arguments)
}
})
Object.isExtensible(proxy)
- preventExtensions()捕获器会在Object.preventExtensions()中被调用。对应反射API方法为Reflect.preventExtensions()
- 使用了
Object.preventExtensions()方法,就不能向对象中新添加属性和方法了,但是可以修改对象原有的属性和方法。
const target = {
id: "foo",
};
const proxy = new Proxy(target,{
preventExtensions(target){
console.log('preventExtensions...');
return Reflect.preventExtensions(...arguments)
}
})
Object.preventExtensions(proxy)
- apply() 捕获器会在调用函数中被调用。对应的反射API为Reflect.apply()
- construct()捕获器会在new操作符中被调用。对应的反射API为Reflect.construct()
1.3 代理模式
1.3.1 跟踪属性访问
- 通过捕获get、set、has等操作。可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过。
const user = {
name: 'Jack'
}
const proxy = new Proxy(user,{
get(target,property,proxy){
console.log('get()...');
return Reflect.get(...arguments)
},
set(target,propety,proxy){
console.log('set()...');
return Reflect.set(...arguments)
},
has(target,property){
console.log('has()...');
return Reflect.has(...arguments)
}
})
proxy.name
proxy.name = 'Nicholas'
1.3.2 隐藏属性
- 代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举
const hiddenProperties = ['foo','bar']
const targetObject = {
foo: 1,
bar: 2,
baz: 3
}
const proxy = new Proxy(targetObject,{
get(target,property){
if(hiddenProperties.includes(property)){
return undefined
}else {
return Reflect.get(...arguments)
}
},
has(target,property){
if(hiddenProperties.includes(property)){
return false
}else{
return Reflect.has(...arguments)
}
}
})
console.log(proxy.foo);
console.log(targetObject.foo);
console.log('foo' in proxy);
console.log('bar' in targetObject);
1.3.3 属性验证
- 因为所有赋值操作都会触发Set()捕获器,所以可以根据所赋值的值决定是允许还是拒绝赋值
const target = {
onlyNumber: 1
}
const proxy = new Proxy(target,{
set(target,property,value){
if(typeof value !== 'number'){
return false
}else {
return Reflect.set(...arguments)
}
}
})
proxy.onlyNumber = 2
console.log(target.onlyNumber);
proxy.onlyNumber = '2'
console.log(proxy.onlyNumber);
1.3.4 函数与构造函数验证
- 跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值。
function median(...nums) {
return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(median, {
apply(target, thisArg, argumentsList) {
for (const arg of argumentsList) {
if (typeof arg !== "number") {
throw "Non-number argument";
}
}
return Reflect.apply(...arguments);
},
});
console.log(proxy(1, 4, 5));
class User {
constructor(id) {
this.id = id;
}
}
const proxy1 = new Proxy(User, {
construct(target, argumentsList, newTarget) {
if (argumentsList[0] == undefined) {
throw "User connot be id";
} else {
return Reflect.construct(...arguments);
}
},
});
new proxy1(1);
new proxy1();
1.3.5 数据绑定与可观察对象
- 通过代理可以把运行时中原本不相关的部分联系到一起,这样就可以实现各种模式,从而让不同的代码互操作。
- 例如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中
const userList = []
class User {
constructor(name){
this.name_ = name
}
}
const proxy = new Proxy(User,{
construct(target,argumentList,newTarget){
const newUser = Reflect.construct(...arguments)
userList.push(newUser)
return newUser
}
})
new proxy('Nicholas')
new proxy('Bob')
new proxy('Jack')
console.log(userList);