Reflect
是一个内置对象,它提供可拦截JavaScript
操作的方法。这些方法与Proxy
处理的方法相同。 Reflect不是函数对象,因此它是不可构造的。
背景及其基本使用
Reflect
解决的问题:
- 提供函数化的方法,用于取代
Object
相关的操作符,如delete
、instanceof
等。
var obj = { foo: "property" };
// 删除obj的foo的属性
// ES5
delete obj.foo
// ES6
Reflect.deleteProperty(target, propertyKey);
- 提供更值得信赖的
Function API
。
在ES5
中,如何我们想要调用某个function F
,同时传入一个可变参数打包成的数组,并且将this
的值绑定到某个object
上,我们只能这样写:
F.apply(obj, args)
如果这个F
是一个object
并且有意或无意间定义了自己的apply
方法,为了确保apply
函数真正被调用,我们可能需要这样写:
// 手动🐶:不管你看不看得懂,反正我看不懂
Function.prototype.apply.call(f, obj, args)
而在ES6
中,我们只需要这样调用:
Reflect.apply(f, obj, args)
- 提供更友好的返回值
// ES5
try {
Object.defineProperty(obj, name, desc);
// 属性定义成功
} catch (e) {
// 意外定义失败,捕获错误
}
// ES6
if (Reflect.defineProperty(obj, name, desc)) {
// 定义成功
} else {
// 定义失败
}
- 提供原生的代理方法,与
Proxy
联动。
当我们使用Proxy对某个对象的行为进行拦截时,通常做一些额外逻辑,然后执行默认行为。
var loggedObj = new Proxy(obj, {
get: function(target, name) {
// 打印对象及其属性
console.log("get", target, name);
// 执行默认行为
return target[name];
}
});
事实上,Proxy
和Reflect
被设计用于联动,每一个Proxy
拦截的行为都对应着一个Reflect API
来执行默认行为:
var loggedObj = new Proxy(obj, {
get: function(target, name) {
console.log("get", target, name);
return Reflect.get(target, name);
}
});
Reflect实战
在了解完Reflect
的基本特点之后,让我们深入了解一下,在实际开发过程中,如何使用Reflect
拦截业务代码调用,延伸出额外的逻辑如:埋点、日志记录、参数预处理等,这里我们使用Typescript
的Decorator
,通过装饰对象方法来实现。
构建装饰器
export function makeClassMemberDecorator(decorator) {
return function (target, name, descriptor) {
const { configurable, enumerable, value, get, initializer } = descriptor;
// 使用Reflect拦截被装饰方法
...
// 使用decorator添加额外的逻辑
...
// 返回descriptor
return {
configurable,
enumerable,
value: decorator(value), // 被装饰的属性/方法
}
};
}
拦截方法被装饰的方法
const { configurable, enumerable, value, get, initializer } = descriptor;
// 保留原有的configurable和enumerable
// 普通方法直接装饰
return {
configurable,
enumerable,
get() {
// 初始化修饰的方法
const resolvedValue = initializer
? Reflect.apply(initializer, this, [])
: Reflect.apply(get, this, []);
// 装饰方法
const decoratedValue = Reflect.apply(decorator, this, [resolvedValue]);
// 覆盖原有方法
Reflect.defineProperty(this, name, {
configurable,
enumerable,
value: decoratedValue,
});
return decoratedValue;
},
};
在decoratedFunc
中添加额外的逻辑
// 创建decorator
const decorator = function(params: any) {
return makeClassMemberDecorator((decoratedFunc: Function) => function(...args: any[]) {
// 编写额外的执行逻辑
const extraFunc = async (...args: any[]) => {
// 日志上报
// await logApi.create(args);
// 参数校验
// const [validation, ...params] = args;
// cosnt isValid = validation(params);
// if (!isValid) throw Error("Parameters Invalid!");
};
// 调用原函数(后置调用)
const fn = Reflect.apply(decoratedFunc, this, args);
// 如果返回Promise
if (Promise && Promise.resolve(fn) === fn) {
// 定义额外执行的函数所需的参数
const packedArgs = [...args, ...params];
return fn
.then(() => Reflect.apply(extraFunc, this, packedArgs))
.then(() => fn)
.catch((error: Error) => {
// 无论如何extraFunc都会执行,且对被装饰的函数是透明的
extraFunc({}, error);
throw error;
});
}
// 直接调用
extraFunc();
// 返回被装饰的方法
return fn;
})
};
装饰器使用
React组件中
class MyComponet extends React.Component<IState, IProps> {
@Decorator((props: IProps, state: IState, args: any[], returnArgs: any) => {
// 处理额外逻辑,不干扰业务逻辑
})
private handleClick = (evt: any) => {
// 业务逻辑
}
render() {
return <Button onClick={handleSubmit}>Submit</Button>
}
}
Node.js应用中,配置Joi
对参数进行校验,同时上传日志.
class Controller {
// 日志上传
@reportor(async (ctx: App.Context, params: any[]) => {
// 上传日志
})
// 参数校验
@validate({
Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
repeat_password: Joi.ref('password'),
access_token: [
Joi.string(),
Joi.number()
],
birth_year: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
}).with('username', 'birth_year')
})
privare myApi(ctx: App.Context) {
// 校验逻辑
}
}
总结
Reflect
提供了很多直接操作javascript
内部属性的途径,可以帮助我们更灵活的来编写代码。希望这篇文章能给你们提供一些灵感。