Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。
想要了解其内容,我们来讲解几个概念。
- MetaData:也称元数据,元数据是用来描述数据的数据。举个例子:元数据概念其实是跟数据库的字段名(field)一致 —— 在传统的数据库中就天然包含元数据的概念。比如name,phone,它们就是元数据。
- Reflect:es6规范中,Reflect已存在,简单来说,这个API的作用就是可以实现对变量操作的函数化,也就是反射,具体可看阮一峰es6关于reflect的教程
- Decorator:装饰器,主要用来扩展类和类的方法,使其功能更强大。具体可看阮一峰es6关于decorator的教程。
由于 JS/TS 现有的 装饰器更多的是存在于对函数或者属性进行一些操作,比如修改他们的值,代理变量,自动绑定 this 等等功能。但是却无法实现通过反射来获取究竟有哪些装饰器添加到这个类/方法上… 这就限制了 JS 中元编程的能力。【元编程:Symbol、Reflect 和 Proxy 是属于 ES6 元编程范畴的,能“介入”的对象底层操作进行的过程中,并加以影响。元编程中的 元 的概念可以理解为 程序 本身。”元编程能让你拥有可以扩展程序自身能力“】
JS 中对 Reflect Metadata 的诉求:
- 其他 C#、Java、Pythone 语言已经有的高级功能,我 JS 也应该要有(诸如C# 和 Java 之类的语言支持将元数据添加到类型的属性或注释,以及用于读取元数据的反射API,而目前 JS 缺少这种能力)
- 许多用例(组合/依赖注入,运行时类型断言,反射/镜像,测试)都希望能够以一致的方式向类中添加其他元数据。
- 为了使各种工具和库能够推理出元数据,需要一种标准一致的方法;
- 元数据不仅可以用在对象上,也可以通过相关捕获器用在 Proxy 上;
- 对开发人员来说,定义新的元数据生成装饰器应该简洁
TypeScript 已经完整的实现了装饰器的声明生成元数据,后续的讲解默认都以 TS 环境。
安装
我们想要使用这个功能,可以借助仓库reflect-metadata,先 npm 安装这个库:
npm install reflect-metadata —save
TypeScript 支持为带有 装饰器 的声明 生成元数据。
在 tsconfig.json里启用emitDecoratorMetadata
基础用法
严格地说,元数据(metadata)和 装饰器(Decorator) 是 EcmaScript 中两个独立的部分。 然而,如果你想实现像是反射这样的能力,你总是同时需要它们。Reflect Metadata 的 API 可以用于类或者类的属性上。
import "reflect-metadata"; @Reflect.metadata('inclass', '1') class Person {
@Reflect.metadata('inmethod', '2') public speak(val: string): string {
return val; } } console.log(Reflect.getMetadata('inclass', Person)) // '1' console.log(Reflect.getMetadata('inmethod', new Person(), 'speak')); // '2'
对照这个例子,我们再引出 Metadata 的四个概念:
| Metadata Key {Any}(简写 k) | 元数据的 Key,对于一个对象来说,它可以有很多元数据,每一个元数据都对应有一个 Key。一个很简单的例子就是说,你可以在一个对象上面设置一个叫做 ‘name’ 的 Key 用来设置他的名字,用一个 ‘created time’ 的 Key 来表示他创建的时间。这个 Key 可以是任意类型。在后面会讲到内部本质就是一个 Map 对象 |
|---|---|
| Metadata Value {Any} (简写 v) | 元数据的值,任意类型都行。 |
| Target {Object} (简写 t) | 表示要在这个对象上面添加元数据 |
| Property {String|Symbol} (简写 p) | 用于设置在哪个属性上添加元数据。大家可能会想,这个是干什么用的,不是可以在对象上面添加元数据了么?其实不仅仅可以在对象上面添加元数据,甚至还可以在对象的属性上面添加元数据。其实大家可以这样理,当你给一个对象定义元数据的时候,相当于你是默认指定了 undefined 作为 Property。 |
metadata 毕竟也属于 “数据”,那么对应的 API 就是跟数据库的 CURD 增删改查的操作相对应的。
对照上面的4个参数,我们来理解API会更容易:
namespace Reflect {
// 用于装饰器 metadata(k, v): (target, property?) => void // 在对象上面定义元数据 defineMetadata(k, v, o, p?): void // 是否存在元数据 hasMetadata(k, o, p?): boolean hasOwnMetadata(k, o, p?): boolean // 获取元数据 getMetadata(k, o, p?): any getOwnMetadata(k, o, p?): any // 获取所有元数据的 Key getMetadataKeys(o, p?): any[] getOwnMetadataKeys(o, p?): any[] // 删除元数据 deleteMetadata(k, o, p?): boolean }
一、创建元数据(Reflect.metadata/Reflect.defineMetadata)
- 通过装饰器声明方式创建,推荐的方式,也是很主流的一种方式(例子在上面基础用法)
- “事后”(类创建完后)再给目标对象创建元数据,代码如下
class Test {
public func(val: string): string {
return val; } } Reflect.defineMetadata('a', '1111', Test); // 给类添加元数据 Reflect.defineMetadata('b', '22222', Test.prototype, 'func');// 给类的属性添加元数据 console.log(Reflect.getMetadata('a', Test)); // 1111 console.log(Reflect.getMetadata('b', Test.prototype, 'func')) // 22222
Reflect.metadata和Reflect.defineMetadata其最本质都是调用源码中 OrdinaryDefineOwnMetadata 方法


二、判断是否存在元数据(hasMetadata/hasOwnMetadata)
它们两者调用方式一样,唯一的区别是前者会包含原型链查找,后者不会查找原型链
console.log(Reflect.hasMetadata('a', Test)); //true console.log(Reflect.hasOwnMetadata('a', Test));//true console.log(Reflect.hasMetadata('b', Test, 'func'));//false console.log(Reflect.hasOwnMetadata('b', Test.prototype, 'func'));//true
三、查询元数据(hasMetadata/hasOwnMetadata)
它们之间的区别前者会包含原型链查找,后者不会查找原型链
console.log(Reflect.getMetadata('a', Test)); //1111 console.log(Reflect.getOwnMetadata('a', Test)); //1111 console.log(Reflect.getMetadata('b', Test, 'func'));//undefined console.log(Reflect.getOwnMetadata('b', Test.prototype, 'func'));//22222
四、删除元数据(deleteMetadata)
console.log(Reflect.deleteMetadata('a', Test)) //true console.log(Reflect.deleteMetadata('b', Test.prototype, 'func'));//true console.log(Reflect.deleteMetadata('a', Test));//false
具体应用
1、控制反转,依赖注入(对控制反转,依赖注入概念不清的可以看下这篇文章:点击链接
type Constructor<T = any> = new (...args: any[]) => T; const Injectable = (): ClassDecorator => target => {
}; class OtherService {
a = 1; } @Injectable() class TestService {
constructor(public readonly otherService: OtherService) {
} testMethod() {
console.log(this.otherService.a); } } const Factory = <T>(target: Constructor<T>): T => {
// 获取所有注入的服务 const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService] const args = providers.map((provider: Constructor) => new provider()); return new target(...args); }; Factory(TestService).testMethod(); // 1
2、controller和getter的实现
const METHOD_METADATA = 'method'; const PATH_METADATA = 'path'; const Controller = (path: string): ClassDecorator => {
return target => {
Reflect.defineMetadata(PATH_METADATA, path, target); } } const createMappingDecorator = (method: string) => (path: string): MethodDecorator => {
return (target, key, descriptor) => {
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value); Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value); } } const Get = createMappingDecorator('GET'); const Post = createMappingDecorator('POST'); @Controller('/test') class SomeClass {
@Get('/a') someGetMethod() {
return 'hello world'; } @Post('/b') somePostMethod() {
return 'zhangjing'; } } function isFunction(arg: any): boolean {
return typeof arg === 'function'; } function isConstructor(arg: string) {
return arg === 'constructor'; } function mapRoute(instance) {
const prototype = Object.getPrototypeOf(instance); // 筛选出类的 methodName const methodsNames = Object.getOwnPropertyNames(prototype) .filter(item => !isConstructor(item) && isFunction(prototype[item])) return methodsNames.map(methodName => {
const fn = prototype[methodName]; // 取出定义的 metadata const route = Reflect.getMetadata(PATH_METADATA, fn); const method = Reflect.getMetadata(METHOD_METADATA, fn); return {
route, method, fn, methodName } }) } console.log(Reflect.getMetadata(PATH_METADATA, SomeClass)); // '/test' console.log(mapRoute(new SomeClass()));
输出:

3、获取类型
TS 中的 reflect-metadata 是经过扩展,额外给我们添加 3 个类型相关的元数据。之所以会有,这是因为我们在 TS 中开启了 emitDecoratorMetadata编译选项,这样 TS 在编译的时候会将类型元数据自动添加上去。这也是 TS 强类型编程带来的额外好处.
| design:type | 被装饰的对象是什么类型, 比如是字符串? 数字? 还是函数 |
|---|---|
| design:paramtypes | 被装饰对象的参数类型, 是一个表示类型的数组, 如果不是函数, 则没有该 key |
| design:returntype | 表示被装饰对象的返回值属性, 比如字符串,数字或函数等 |
在 vue-property-decorator,通过使用 Reflect.getMetadata API,Prop Decorator 能获取属性类型传至 Vue


如果你用 ES6 编程,需要自己加这 3 个元数据,代码如下:
// 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; } } // Metadata introspection let obj = new C("a", 1); let type = Reflect.getMetadata("design:type", obj, "add"); //Function() {} let paramTypes = Reflect.getMetadata("design:paramtypes", obj, "add"); // [Number, Number] let returntype = Reflect.getMetadata("design:returntype", obj, "add"); // Number() {} console.log(type, paramTypes, returntype);
添加元数据,让对象拥有一个新的 [[Metadata]] 内部属性,包含一个 Map,这个 Map 的 key 是属性的 key 或者 undefined,值是 源数据的 key 以及相应的 value 组成的 Maps。从数据结构上我们可以看出其设计理念也很清晰:给对象添加额外的信息,但是不影响对象的结构 —— 这一点很重要,当你给对象添加了一个原信息的时候,对象是不会有任何的变化的,不会多 property,也不会有的 property 被修改了。但却可以衍生出很多其他的用途(比如可以让装饰器拥有真正装饰对象而不改变对象的能力,让对象拥有更多语义上的功能)
具体存储的位置:
- 当在类 C 本身上使用 metadata 的时候,元数据会存储在 C.[[Metadata]] 属性中,其对应的 property 值是 undefined
- 通过类声明的静态成员(members)定义的源数据会存在 C.[[Metadata]], 以该属性(property)名作为 key。(上述例子我没用过静态成员去定义元数据,大家可以试试)
- 定义在类 C 实例成员上的元数据,那么元数据会存储在C.prototype.[[Metadata]] 属性中,以该属性(property)名作为 key
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/227687.html原文链接:https://javaforall.net
