代理基础
ES 6
中新增了代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。可以给目标对象定义一个关联一个代理对象,而这个代理对象抽象的目标对象来使用。在对目标对象的跟踪操作影响到目标对象之前,可以在代理对象中对这些操作进行控制
创建空代理
最简单的代理就是空代理,在这种情况下,在代理对象上进行的所有操作都会无障碍的传播到目标对象上。
使用 Proxy
构造函数创建代理,改构造函数接收两个参数:目标对象和处理程序对象。
const target = {
name: "hui"
}
const handler = {}
const proxy = new Proxy(target, handler)
// 下面对 proxy 对象的所有的操作都会作用到 target 对象上
console.log(proxy.name) // hui
proxy.age = 24
console.log(target.age) // 24
console.log(proxy.age) // 24
注意: Proxy.prototype
是 undefined
, 因此不能使用 instanceof
操作符;使用严格相等(===) 可以区分代理对象和目标对象
console.log(proxy instanceof Proxy); // TypeError
捕获器
捕获器是在处理程序对象中定义的 基本操作的拦截器。每个处理程序对象能够定义零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。代理可以在这些操作传播到目标对象之前调用捕获器函数,从而拦截并修改相应的行为。
定义捕获器
下面的例子中,定义了一个 get
捕获器,当通过代理对象执行 get()
操作时,就会触发 get
捕获器。只有在代理对象上执行这些操作才会触发捕获器,在目标对象上直接执行这些操作仍然会产生正常的行为。
const target = {
name: "hui"
}
const handler = {
get(){
return "proxy handler"
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.name) // proxy handlers
捕获器参数和反射 API
所有的捕获去都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。比如,上面提到的 get
捕获器会接收到目标对象,要查询的属性和代理对象三个参数
const target = {
name: "hui"
}
const handler = {
get(target, property, proxy){
//
return target[property]
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.name) // hui
所有的捕获器都可以基于自己的参数重建原始操作,但是有些捕获器的行为比较复杂,我们可以通过捕获器对应的反射 (Reflect) API
方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。因此可以很实用反射 API
想下面这样定义代理对象
const target = {
name: "hui"
}
const handler = {
get(){
return Reflect.get(...arguments) + ' zhong'
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.name) // hui zhong
捕获器不变式
捕获器处理程序的行为必须遵循 捕获器不变式。捕获器不变式因方法不同而不同,但通常都会放置捕获器定义出现过于翻唱的行为。
例如, 如果目标对象上有一个不可配置且不可写的数据属性,那么在捕获器返回一个与改属性不同的值时,会抛出错误
const target = {}
Object.defineProperty(target, 'name', {
configurable: false,
writable: false,
value: 'hui'
})
const handler = {
get(){
return "proxy handler"
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.name) // TypeError
可撤销代理
对于使用 new Proxy 创建的普通代理来说,这种联系会在代理对象的生命周期内一直存在。 Proxy 暴露了 revocable() 方法,这个方法支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的。
另外, 撤销函数 (revoke) 是幂等的,无论调用多少次结果都是一样的。在撤销代理之后,再次调用代理会抛出 TypeError 错误
const target = {
foo: "bar"
}
const handler = {
get(){
return "intercepted"
}
}
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.foo)
revoke()
revoke() // revoke 方法是幂等的。调用多少次结果都是一样的
console.log(proxy.foo) // TypeError 撤销代理之后,再次调用代理会抛出错误
实用反射 API
反射 API
并不仅限于捕获处理程序,大多数反射 API
在 Object
类型上有对应的方法,反射方法实用于细粒度的对象控制和操作。
状态标记
很多反射方法会返回一个布尔值,这个布尔值称为状态标记,表示意图执行的操作是否成功。可以使用状态标记来重构那些抛出错误的代码
// 初始代码
const target ={}
try{
Object.defineProperty(target, 'name', {value: 'hui'})
console.log('success')
}catch(err){
console.log(err)
}
// 使用反射方法重构上面代码
if(Reflect.defineProperty(target, 'name', {value: 'hui'})){
console.log('success')
}else{
console.log('failed')
}
一下方法都会提供状态标记
- Reflect.defineProperty()
- Reflect.preventExtensions()
- Reflect.setProperty()
- Reflect.set()
- Reflect.deleteProperty()
是用一等函数替代操作符
- Reflect.get() : 可以替代对象属性访问操作符
- Reflect.set() : 可以替代复制操作符
- Reflect.has() : 可以替代
in
操作符或者with()
- Reflect.deleteProperty() : 可以替代 delete 操作符
- Reflect.construct() : 可以替代
new
操作符
安全地应用函数
在通过 apply
方法调用函数是,被调用的函数也可能定义了自己 apply
方法,为避免这种问题,可以调用 Function
原型上的 apply
方法
Function.property.apply.call(myFunc, thisVal, args)
上面冗长的代码可以通过 Reflect.apply
来避免
Reflect.apply(myFunc, thisVal, args)