代理与反射-反射
一 反射的基本使用
1.1 反射概念
在 JS 运行时,能够知道这个对象的成员,并能够调用这些成员,这种动态获取信息、动态调用对象方法的功能称为反射。
反射 API 范围很广,可以用于代理中的捕获处理程序。大多的反射 API 方法在 Object 类型上都有对应的方法。相比 Object 直接操作,反射更适合用于细粒度的对象控制。
1.2 反射的状态标记
很多反射方法返回一个布尔值,表示意图执行的操作是否成功,称作“状态标记”的布尔值。有时候,状态标记比那些返回修改后的对象或者抛出错误(取决于方法)的反射 API 方法更有用。
例如在定义新属性时如果发生问题, Reflect.defineProperty()会返回 false,而不是抛出错误,以下是使用 Object 原生方法的实现该需求:
// 使用 Object 原生方法:
const o = {}
try {
Object.defineProperty(o, 'foo', { value: 'bar' })
console.log('success')
} catch (e) {
console.log('failure')
}
可以使用反射 API 对下面的代码进行重构:
const o = {}
if (Reflect.defineProperty(o, 'foo', { value: 'bar' })) {
console.log('success')
} else {
console.log('failure')
}
以下反射方法都会提供状态标记:
Reflect.defineProperty()
Reflect.preventExtensions()
Reflect.setPrototypeOf()
Reflect.set()
Reflect.deleteProperty()
1.3 替代操作符的方法
以下反射方法提供只有通过操作符才能完成操作:
// 可以替代对象属性访问操作符
Reflect.get()
// 可以替代=赋值操作符
Reflect.set()
// 可以替代 in 操作符或 with()
Reflect.has()
// 可以替代 delete 操作符
Reflect.deleteProperty()
// 可以替代 new 操作符
Reflect.construct()
1.4 函数安全应用
在通过 apply 方法调用函数时,被调用的函数可能也定义了自己的 apply 属性(虽然可能性极小)。为绕过这个问题,可以使用定义在 Function 原型上的 apply 方法,比如:
Function.prototype.apply.call(myFunc, thisVal, argumentList)
使用反射可以解决上述问题:
Reflect.apply(myFunc, thisVal, argumentsList)
二 代理与反射的配合
2.1 优雅地书写捕获器
代理的捕获器并不是都像 get() 这么简单,手动重建原始行为是不现实的,可以通过调用全局 Reflect 对象上(封装了原始行为)的同名方法来轻松重建。
处理程序对象中所有可以捕获的方法都有对应的反射( Reflect) API 方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。因此,使用反射 API 也可以像下面这样定义出空代理对象:
const target = {
foo: 'bar',
}
const handler = {
get() {
console.log('handler override')
return Reflect.get // Reflect.get(...arguments) 的简写
},
}
const proxy = new Proxy(target, handler)
console.log(proxy.foo) // 'handler override' bar
如果直接将全局反射对象作为处理程序对象,那么所有可捕获的方法将被捕获:
const target = {
foo: 'bar',
}
const proxy = new Proxy(target, Reflect)
console.log(proxy.foo) // bar
反射 API 为开发者准备好了样板代码,在此基础上开发者可以用最少的代码修改捕获的方法。 比如,下面的代码在某个属性被访问时,会对返回的值进行一番修饰:
const target = {
foo: 'bar',
baz: 'qux',
}
const handler = {
get(trapTarget, property, receiver) {
let decoration = ''
if (property === 'foo') {
decoration = '!!!'
}
return Reflect.get + decoration
},
}
const proxy = new Proxy(target, handler)
console.log(proxy.foo) // bar!!!
console.log(target.foo) // bar
console.log(proxy.baz) // qux
console.log(target.baz) // qux
2.2 代理另外一个代理
代理可以拦截反射 API 的操作,而这意味着完全可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网:
const target = {
foo: 'bar',
}
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)
},
})
// second proxy first proxy bar
console.log(secondProxy.foo)