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

栏目: JavaScript · 发布时间: 5年前

内容简介:本篇内容包括如下部分:关于反射的概念,摘自在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

本篇内容包括如下部分:

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

为什么JavaScript中需要反射?

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

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

可见反射机制对于依赖注入、运行时类型断言、测试是非常有用的,同时随着基于JavaScript的应用做的越来越大,使得我们希望有一些 工具 和特性可以用来应对增长的复杂度,例如控制反转,运行时类型断言等。但由于JavaScript语言中没有反射机制,所以导致这些东西要么没法实现,要么实现的不如 C#Java 语言实现的强大。

强大的反射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}`);
}

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

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

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

超预期

超预期

[美] 莱昂纳多·因基莱里、迈卡·所罗门 / 杨波 / 后浪丨江西人民出版社 / 2017-11 / 45.00元

用户体验决定产品成败,只有超预期才能赢得好口碑! 互联网大佬一致推崇的打造爆款产品及服务的核心理念 ................... ※编辑推荐※ ☆ 超预期,才有用户体验,互联网大佬一致推崇的打造爆款产品及服务的核心理念 - 周鸿祎:“什么叫用户体验?超过用户预期才叫 体验!” - 雷军:“口碑的真谛是超越用户的期望值。” - 马化腾:“用户体验,......一起来看看 《超预期》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具