装饰器与元数据反射(4)元数据反射

本篇内容包括以下部分:npm

  1. 为何JavaScript中须要反射
  2. 元数据反射API
  3. 基本类型序列化
  4. 复杂类型序列化

为何JavaScript中须要反射?

关于反射的概念,摘自百度百科json

在计算机科学领域,反射是指一类应用,它们可以自描述和自控制。也就是说,这类应用经过采用某种机制来实现对本身行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

可见反射机制对于依赖注入、运行时类型断言、测试是很是有用的,同时随着基于JavaScript的应用作的愈来愈大,使得咱们但愿有一些工具和特性能够用来应对增加的复杂度,例如控制反转,运行时类型断言等。但因为JavaScript语言中没有反射机制,因此致使这些东西要么无法实现,要么实现的不如C#Java语言实现的强大。bash

强大的反射API容许咱们能够在运行时测试一个未知的类,以及找到关于它的任何信息,包括:名称、类型、接口等。虽然可使用诸如Object.getOwnPropertyDescriptor()Object.keys()查询到一些信息,但咱们须要反射来实现更强大的开发工具。庆幸的是,TypeScript已经支持反射机制,来看看这个特性吧函数

元数据反射API

能够经过安装reflect-metadata包来使用元数据反射的API工具

npm install reflect-metadata;

若要使用它,咱们须要在tsconfig.json中设置emitDecoratorMetadatatrue,同时添加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}`);
}

接下来将其应用到一个类的属性上,以获取其类型:code

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[];

这种信息能够用来在运行时验证一个实例是否实现了特定的接口,而这个功能对于一个依赖翻转容器特别的有用。

相关文章
相关标签/搜索