元数据
元数据是用来定义数据的数据。例如,对于一个数据A,它会具有值,数据类型等等描述这个数据的数据。这样的数据,我们称之为元数据。
在 JS 当中,也就是所谓的装饰器。通过反射来获取类属性上面的批注。但是 JS 的装饰器更多的是存在于对函数或者属性进行一些操作,比如修改他们的值,代理变量,自动绑定 this 等等功能。但是却无法实现通过反射来获取究竟有哪些装饰器添加到这个类/方法上,这时候就需要 Reflect Metadata 。
Relfect Metadata
使用 Relfect Metadata,你可以通过装饰器来给类添加一些自定义的信息。然后通过反射将这些信息提取出来。当然你也可以通过反射来添加这些信息。
import "reflect-metadata";
@Reflect.metadata('inClass', 'A')
class Test {
@Reflect.metadata('inMethod', 'B')
public hello(): string {
return 'hello world';
}
}
console.log(Reflect.getMetadata('inClass', Test)); // 'A'
console.log(Reflect.getMetadata('inMethod', new Test(), 'hello')); // 'B'
获取类型信息
获取属性类型
Reflect.getMetadata('design:type', target, key):获取属性类型
import "reflect-metadata";
function logType(target: any, key: string) {
const t = Reflect.getMetadata("design:type", target, key);
console.log(`${key} type: ${t.name}`); // 会打印出 attr1 type: String
}
class Demo {
@logType
public attr1 : string;
}
获取函数参数类型
Reflect.getMetadata("design:paramtypes", target, key):获取函数参数类型
import "reflect-metadata";
function logParamTypes(target: any, key: string) {
const types = Reflect.getMetadata("design:paramtypes", target, key);
const s = types.map(a => a ? a.name : 'undefined').join('\n ');
console.log(`${key} param types: ${s}`);
// 打印出:
// doSomething param types:
// Number
// String
// Boolean
// Object
// undefined
// Array
// Array
// Foo
// String
// Function
// Object
}
class Foo {}
interface IFoo {}
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
class Demo {
@logParamTypes
doSomething(
param1: number,
param2: string,
param3: boolean,
param4: any,
param5: void,
param6: [],
param7: [string, number],
param8: Foo,
param9: Direction,
param10: () => 0,
param11: IFoo,
): number {
return 1;
}
}
/**
序列化规则
number 序列化为 Number
string 序列化为 String
boolean 序列化为 Boolean
any 序列化为 Object
void 序列化为 undefined
Array 序列化为 Array
Tuple序列化为 Array
class 序列化为类构造函数
Enum 序列化为 Number
如果至少有一个签名,则序列化为 Function
否则序列化为 Object(包括接口)
*/
获取函数返回值类型
Reflect.getMetadata("design:returntype", target, key):获取函数返回值类型
import "reflect-metadata";
function returnType(target: any, key: string) {
const type = Reflect.getMetadata("design:returntype", target, key);
console.log(`${key} return type: ${type.name}`);
// 打印出:doSomething return type: Number
}
class Demo {
@returnType
doSomething(param: number): number {
return 1;
}
}
自定义元数据
用途
其实所有的用途都是一个目的,给对象添加额外的信息,但是不影响对象的结构。这一点很重要,当你给对象添加了一个元信息的时候,对象是不会有任何的变化的,不会多 property,也不会有的 property 被修改了。可以衍生出很多其他的用途。
API
namespace Reflect {
// 用于装饰器
function metadata(metadataKey: any, metadataValue: any): {
(target: Function): void;
(target: Object, propertyKey: string | symbol): void;
};
// 在对象或属性上面定义元数据
function defineMetadata(metadataKey: any, metadataValue: any, target: Object): void;
function defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey: string | symbol): void;
// 检查对象或属性的原型链上是否存在元数据
function hasMetadata(metadataKey: any, target: Object): boolean;
function hasMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
// 检查对象或属性的原型链上是否存在自己的元数据
function hasOwnMetadata(metadataKey: any, target: Object): boolean;
function hasOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
// 获取对象或属性上的元数据键的元数据值
function getMetadata(metadataKey: any, target: Object): any;
function getMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
// 获取对象或属性上自己的元数据键的元数据值
function getOwnMetadata(metadataKey: any, target: Object): any;
function getOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
// 获取对象或属性原型链上的所有元数据键
function getMetadataKeys(target: Object): any[];
function getMetadataKeys(target: Object, propertyKey: string | symbol): any[];
// 获取对象或属性的所有自己的元数据键
function getOwnMetadataKeys(target: Object): any[];
function getOwnMetadataKeys(target: Object, propertyKey: string | symbol): any[];
// 从对象或属性中删除元数据
function deleteMetadata(metadataKey: any, target: Object): boolean;
function deleteMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
}
// 需要在 tsconfig.json 配置的开关:
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
}
注: 类似于类的继承,查找元数据的方式也是通过原型链进行的。
class A {
@Reflect.metadata('name', 'hello')
hello() {}
}
const t1 = new A()
const t2 = new A()
Reflect.defineMetadata('otherName', 'world', t2, 'hello')
Reflect.getMetadata('name', t1, 'hello') // 'hello'
Reflect.getMetadata('name', t2, 'hello') // 'hello'
Reflect.getMetadata('otherName', t2, 'hello') // 'world'
Reflect.getOwnMetadata('name', t2, 'hello') // undefined
Reflect.getOwnMetadata('otherName', t2, 'hello') // 'world'