本篇内容包括如下部分:
为什么 JavaScript 中需要反射
元数据反射 API
基本类型序列化
复杂类型序列化
为什么 JavaScript 中需要反射?
关于反射的概念,摘自百度百科
在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
可见反射机制对于依赖注入、运行时类型断言、测试是非常有用的,同时随着基于 JavaScript 的应用做的越来越大,使得我们希望有一些工具和特性可以用来应对增长的复杂度,例如控制反转,运行时类型断言等。但由于 JavaScript 语言中没有反射机制,所以导致这些东西要么没法实现,要么实现的不如 C# 或 Java 语言实现的强大。
强大的反射 API 允许我们可以在运行时测试一个未知的类,以及找到关于它的任何信息,包括:名称、类型、接口等。虽然可以使用诸如 Object.getOwnPropertyDescriptor()和 Object.keys()查询到一些信息,但我们需要反射来实现更强大的开发工具。庆幸的是,TypeScript 已经支持反射机制,来看看这个特性吧
元数据反射 API
可以通过安装 reflect-metadata 包来使用元数据反射的 API
npm install reflect-metadata;
若要使用它,我们需要在 tsconfig.json 中设置 emitDecoratorMetadata 为 true,同时添加 reflect-metadata.d.ts 的引用,同时加载 Reflect.js 文件。然后我们来实现装饰器并使用反射元数据设计的键值,目前可用的有:
类型元数据:design:type
参数类型元数据:design:paramtypes
返回类型元数据:design:returntype
我们来通过一组例子来说明
1)获取类型元数据
首先声明如下的属性装饰器:
function logType(target : any, key : string) {
var t = Reflect.getMetadata(“design:type”, target, key);
console.log(`${key} type: ${t.name}`);
}
接下来将其应用到一个类的属性上,以获取其类型:
class Demo{
@logType
public attr1 : string;
}
这个例子将会在控制台中打印如下信息:
attr1 type: String
2) 获取参数类型元数据
声明参数装饰器如下:
function logParamTypes(target : any, key : string) {
var types = Reflect.getMetadata(“design:paramtypes”, target, key);
var s = types.map(a => a.name).join();
console.log(`${key} param types: ${s}`);
}
然后将它应用在一个类方法的参数上,用以获取所装饰参数的类型:
class Foo {}
interface IFoo {}
class Demo{
@logParameters
param1 : string,
param2 : number,
param3 : Foo,
param4 : {test : string},
param5 : IFoo,
param6 : Function,
param7 : (a : number) => void,
) : number {
return 1
}
}
这个例子的执行结果是:
doSomething param types: String, Number, Foo, Object, Object, Function, Function
3) 获取返回类型元数据
同样的我们可以使用 ”design:returntype” 元数据键值,来获取一个方法的返回类型:
Reflect.getMetadata(“design:returntype”, target, key);
基本类型序列化
让我们回看上面关于 ”design:paramtypes” 的例子,注意到接口 IFoo 和对象字面量 {test: string} 被序列化为 Object,这是因为 TypeScript 仅支持基本类型的序列化,基本类型序列化规则如下:
number 序列化为 Number
string 序列化为 String
boolean 序列化为 Boolean
any 序列化为 Object
void 序列化为 undefined
Array 序列化为 Array
元组 Tuple 序列化为 Array
类 class 序列化为类的构造函数
枚举 Enum 序列化为 Number
剩下的所有其他类型都被序列化为 Object
接口和对象字面量可能在之后的复杂类型序列化中会被做具体的处理。
复杂类型序列化
TypeScript 的团队为复杂类型的元数据序列化做出了努力。上面列出的序列化规则对基本类型依然适用,但对复杂类型提出了不同的序列化逻辑。如下是通过一个例子来描述所有可能的类型:
interface _Type {
/**
* Describes the specific shape of the type.
* @remarks
* One of: “typeparameter”, “typereference”, “interface”, “tuple”, “union” or “function”.
*/
kind: string;
}
我们也可以找到用于描述每种可能类型的类,例如用于序列化通用接口 interface foo<bar>:
// 描述一个通用接口
interface InterfaceType extends _Type {
kind: string; // “interface”
// 通用类型参数. 可能为 undefined.
typeParameters?: TypeParameter[];
// 实现的接口.
implements?: Type[];
// 类型的成员 可能为 undefined.
members?: {[key: string | symbol | number]: Type; };
// 类型的调用标识. 可能为 undefined.
call?: Signature[];
// 类型的构造标识. 可能为 undefined.
construct?: Signature[];
// 类型的索引标识. 可能为 undefined.
index?: Signature[];
}
这里有一个属性指出实现了哪些接口
// 实现的接口
implements?: Type[];
这种信息可以用来在运行时验证一个实例是否实现了特定的接口,而这个功能对于一个依赖翻转容器特别的有用。