「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」
前一篇文章介绍了 Reflect 基础 API 的内容,使用 Reflect 可以为对象设置属性,这个属性是会被直接添加到对象上的,有时候我们需要添加一种元数据,这种数据是用来描述对象本身,不应该出现在对象上。基于这种需求,有一个关于 metadata 的提案,这个提案在 Reflect 对象上扩展了元数据的能力。元数据是对反射功能的一个补充,在一些框架开发场景下很有用,由于这个功能还没有stable,我们可以引入 reflect-metadata 库来使用元数据能力。
meta-data 提案中包含以下方法:
- Reflect.defineMetadata
- Reflect.hasMetadata
- Reflect.hasOwnMetadata
- Reflect.getMetadata
- Reflect.getOwnMetadata
- Reflect.getMetadataKeys
- Reflect.deleteMetadata
从方法名可以看出他们的作用,和常规的数据定义方法名风格是一致的,只是这里定义的是元数据信息元数据不影响程序运行,只能使用元数据相关 API 可以操控元数据。
查看 meta-data 源码可以看到实际上内部是利用一个 WeakMap 来实现的,元数据被存储在一个独立的 WeakMap 中,这样不影响程序本身,只在想获取时去读取即可。这里利用了 map 的数据结构可以接收对象作为 key 的特点。
除了之前提到的方法,Reflect 中还提供了一个装饰器,使用 @Reflect.metadata(metadataKey, metadataValue) 可以直接添加元数据,结合装饰器可以更容易进行元数据编程。使用 @Reflect.metadata 装饰器,我们可以很方便的对一个类的成员方法进行标识:
// Design-time type annotations
function Type(type) { return Reflect.metadata("design:type", type); }
function ParamTypes(...types) { return Reflect.metadata("design:paramtypes", types); }
function ReturnType(type) { return Reflect.metadata("design:returntype", type); }
// Decorator application
@ParamTypes(String, Number)
class C {
constructor(text, i) {
}
@Type(String)
get name() { return "text"; }
@Type(Function)
@ParamTypes(Number, Number)
@ReturnType(Number)
add(x, y) {
return x + y;
}
}
这样通过 Reflect.getMetadata 就可以获取到标识的字段类型、参数类型、返回值类型。
在 typescript 中,编译器可以为我们自动添加上面的三种元数据类型:只需要在 tsconfig 文件中设置 emitDecoratorMetadata 值为 true 即可:
// typescript
class Demo {
@LogMethod
public foo(bar: number) {
// do nothing
}
}
// without emitDecoratorMetadata
class Demo {
foo(bar) {
// do nothing
}
}
__decorate([
LogMethod
], Demo.prototype, "foo", null);
// with emitDecoratorMetadata
class Demo {
foo(bar) {
// do nothing
}
}
__decorate([
LogMethod,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", void 0)
], Demo.prototype, "foo", null);
可以看到当开启 emitDecoratorMetadata 时,在装饰器中会自动添加 design:type、design:paramtypes、design:returntype 三种类型的元数据信息,我们可以通过 Reflect.getMetadata 直接获取相关的类型信息。
元数据编程多用于框架开发,有时需要实现动态加载,或者需要添加一些标识信息,这些场景通常会使用元数据来实现,开源框架 InversifyJS、angular、nest 中都有使用。
最后还是要注意这个提案还没进入标准,目前使用的是 reflect-metadata 库的实现版本,可能以后相关的 API 会有变化。