js 代理和反射
代理基础
代理是目标对象的抽象。代理类似于c++的指针,可以通过代理操作对象,但是它又是一个独立的变量。在代理上的修改会反映到目标对象上,在目标对象上的修改也会对代理起作用
创建
const target = |
代理的一些不足
由于代理和原对象毕竟是两个对象,所以在涉及this时可能会出现问题const wm = ew WeakMap();
class User
{
constructor(user_id)
{
wm.set(this.user_id);
}
set id(user_id)
{
wm.set(this, user_id);
}
get id()
{
wm.get(this);
}
}
const user = new User(123);
console.log(user.id);//123
const userInstanceProxy = new Proxy(user, {});
console.log(userInstanceProxy.id);//undefine
这是因为user和userInstanceProxy对象不同,因此this不同,导致无法检索
捕获器(trap)
使用代理主要就是为了定义捕获器。捕获器是target中基本操作的拦截器。每次调用代理时,首先会调用捕获器函数,从而进行拦截和修改。捕获器类似于sql中的触发器
例如const target =
{
foo: 'bar'
}
const handler =
{
get()
{
return 'handler override';
}
}
const proxy = new Proxy(target, handler);
console.log(proxy.foo);//handler override
console.log(proxy['foo']);//handler override
console.log(Object.create(proxy)['foo']);//handler override
通过定义get()捕获器,之后每次执行get操作时,都会触发get捕获器.诸如.
、[]
等操作都认为是get操作。但是在原对象上执行的操作不受影响。
捕获器可以访问当前是在操作哪个变量,从而执行相应的操作,例如const target =
{
foo: 'bar'
};
const handler =
{
get(rapTarget, property, receiver)//trapTarget是代理的目标对象,receiver是代理自己
{
console.log(trapTarget === target);
console.log(property);
console.log(receiver === proxy);
}
};
const proxy = new Proxy(target, handler);
proxy.foo;//true, foo, true
捕获器不变式
捕获器可以捕获大部分基本操作,但是有一些操作会导致异常。捕获器不变式就是保证不会出现异常的约束。
例如:目标对象有一个不可写的属性,如果捕获器中返回了一个其他值,那么就会出现TypeErrorconst target = {};
Object.defineProperty(target, 'foo',{
configurable: false,
writable: false,
value: 'bar'
});
const handler =
{
get()
{
return 'qux';
}
}
const proxy = new Proxy(target, handler);
console.log(proxy.foo);//TypeError
捕获器总共可以捕获13种基本操作
get()
get会拦截获取属性值的操作,对应的反射方法为Reflect.get()
- get(target, property, receiver): target是目标对象,property是操作属性的名字,receiver是带你对象
拦截的操作有:
- proxy.property
- proxy[property]
- Object.create(proxy)[property]
- Reflect.get(proxy, property, receiver)//反射是调用原函数
set
set拦截设置属性值的操作,反射方法为Reflect.set()
- bool set(target, property, value, receiver): value是设置的值,返回值为true代表操作成功,false表示操作失败
拦截的操作:
- proxy.property = value
- proxy[property] = value
- Object.create(proxy)[property] = value
- Reflect.set(proxy, property, value, receiver)
捕获器不变式(约束):
- 如果target的property不可写且不可配置,则不能修改目标属性的值
- 如果target.property不可配置并且Post not found: Set为undefined,则不能修改属性值
has
has会捕获in操作符中的操作,反射为Reflect.has()
- bool has(target, property): 返回值表示这个属性是否存在
拦截的操作:
- property in proxy
- property in Object.create(proxy)
- with(proxy) { (property);}
- Reflect.has(proxy, property);
不变式:
- 如果target.property存在且不可配置,则必须返回true
- target.property存在且目标对象不可扩展(不能添加新的属性,通过Object.preventExtensions(object)设置),则必须返回true
defineProperty
拦截Object.defineProperty(),反射方法为Reflect.defineProperty()
- bool defineProperty(target, property, descriptor): 返回值表示是否成功定义。descriptor有enumrable、configurable、writable、value、get、set
const my Target = {}; |
不变式:
- 如果目标对象不可扩展,则无法定义属性
- 如果有一个可配置属性,不可添加同名的不可配置属性
- 如果有一个不可配置属性,不可添加同名的配置属性
getWonPropertyDescriptor
捕获Object.getOwnPropertyDescriptor()。反射方法为Relfect.getOwnPropertyDescriptor()
- Object getOwnPropertyDescriptor(target, property): 返回描述对象,如果对象不存在则返回undefined
不变式:
- target.property存在且可配置,这必须返回对应对象
- target.property存在且target不可扩展,则处理程序必须返回一个表示该属性存在的对象
- target.property不存在,则返回undefined
deleteProperty
会在delete操作符中调用,对应的反射方法为Reflect.deleteProperty()
- bool deleteProperty(target, property): 返回值表示删除属性是否成功
不变式:
- 如果target.property存在且不可配置,则处理程序不能删除这个属性
onwKeys
拦截Object.keys()(返回对象属性的属性名),反射方法为Reflect.ownKeys()
- list ownKyes(target): 返回可枚举对象
不变式:
- 返回的枚举对象必须包含所有的不可配置的自有属性
- 如果对象不可扩展,则返回可枚举对象必须准确包含自有属性键
getPrototypeOf
捕获获得prototype的操作。反射API为Reflect.getPrototypeOf()
- object getPrototypeOf(target): 返回值是原型对象或者是NULL
拦截的操作:
- Object.getPrototypeOf(proxy)
- Reflect.getPrototypeOf(proxy)
proxy.__proto__
- Object.prototype.isPrototypeOf(proxy)
- proxy instanceof Object
捕获器不变式:
- 如果target不可扩展,则唯一有效的返回值是Object
setPrototypeOf
捕获Object.setPrototypeOf(),反射的API为Reflect.setPrototypeOf()
- bool setPrototypeOf(target, prototype): 返回值表示设置是否成功
不变式:
- 如果target不可扩展,则唯一有效的参数是这个对象的原型
isExtensible
捕获Object.isExtensible,反射API为Reflect.isExtensible()
- bool isExtensible(target): 返回值表示target是否可扩展
不变式:
- 如果target可扩展,则处理程序必须返回true
- 如果target不可扩展,则处理程序必须返回false
preventExtensions
…
返回值表示target是否已经不可扩展
不变式: 如果Object.isExtensible(proxy)是false,那么处理程序必须返回true
apply
它会在调用函数时调用,反射方法为Reflect.apply()
- apply(target, thisArg, argumentsList): thisArg是调用函数时的this参数,返回值任意
拦截的操作:
- proxy(…argumentList)
- Fuction.prototype.apply(thisArg, argumentList)
- Function.prototype.call(thisArg, …argumentsList)
- Reflect.apply(target, thisArgument, argumentList)
不变式:
- target必须是一个函数对象
construct
它会在new操作符中被调用,反射方法为Reflect.construct()
- Object construct(target, argumentsList, newTarget): target是目标构造函数,newTarget是最初被调用的构造函数(可能子类调用了父类的构造函数)。返回值是构造完成的对象
不变式:
- target必须可以被用作构造函数
代理的应用
跟踪属性访问
我们可以通过捕获get、set和has等操作,对对象进行监控
const user = |
隐藏属性
因为代理内部对外部代码是不可见的,所以可以很方便的隐藏对象属性.此外,还可以通过set拒绝属性访问,通过apply对函数参数进行审查。const hidden = ['foo', 'bar'];
const targetObject =
{
foo: 1,
bar: 2,
baz: 3
}
const proxy = new Proxy(targetObject, {
get(target, property)
{
if(hidden.includes(property))
{
return Reflect.get(...arguments);
}
else
{
return undefined;
}
}
});
数据绑定
可以在构造时就添加入列表,避免错误。
const list = [] |
反射
在java或c#语言中,反射的含义是通过函数名字字符串找到对应的函数.例如const target =
{
foo: 'bar';
};
const handler =
{
get()
{
return Reflect.get(...arguments);//通过Reflect.get反射到了target的get方法并且可以直接访问原对象
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo);//bar
console.log(proxy.foo);//bar
我们可以使用这种方法获得本应该获得的结果,并且还可以加上一些修饰,例如
get()
{
return Reflect.get(...arguments) + "!";
}