es7之Reflect Metadata

es7之Reflect MetadataReflectMetad 是 ES7 的一个提案 它主要用来在声明的时候添加和读取元数据 想要了解其内容 我们来讲解几个概念 MetaData 也称元数据 元数据是用来描述数据的数据 举个例子 元数据概念其实是跟数据库的字段名 field 一致 在传统的数据库中就天然包含元数据的概念 比如 name phone 它们就是元数据 Reflect es6 规范中 Reflect 已存在 简单来说 这个 API 的作用就是可以实现对变量操作的函数化 也就是反射 具体可看阮一峰 es6 关于 reflec

Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据

想要了解其内容,我们来讲解几个概念。

  1. MetaData:也称元数据,元数据是用来描述数据的数据。举个例子:元数据概念其实是跟数据库的字段名(field)一致 —— 在传统的数据库中就天然包含元数据的概念。比如name,phone,它们就是元数据。
  2. Reflect:es6规范中,Reflect已存在,简单来说,这个API的作用就是可以实现对变量操作的函数化,也就是反射,具体可看阮一峰es6关于reflect的教程
  3. Decorator:装饰器,主要用来扩展类和类的方法,使其功能更强大。具体可看阮一峰es6关于decorator的教程。

由于 JS/TS 现有的 装饰器更多的是存在于对函数或者属性进行一些操作,比如修改他们的值,代理变量,自动绑定 this 等等功能。但是却无法实现通过反射来获取究竟有哪些装饰器添加到这个类/方法上… 这就限制了 JS 中元编程的能力。【元编程:Symbol、Reflect 和 Proxy 是属于 ES6 元编程范畴的,能“介入”的对象底层操作进行的过程中,并加以影响。元编程中的 元 的概念可以理解为 程序 本身。”元编程能让你拥有可以扩展程序自身能力“】


JS 中对 Reflect Metadata 的诉求:

  1. 其他 C#、Java、Pythone 语言已经有的高级功能,我 JS 也应该要有(诸如C# 和 Java 之类的语言支持将元数据添加到类型的属性或注释,以及用于读取元数据的反射API,而目前 JS 缺少这种能力)
  2. 许多用例(组合/依赖注入,运行时类型断言,反射/镜像,测试)都希望能够以一致的方式向类中添加其他元数据。
  3. 为了使各种工具和库能够推理出元数据,需要一种标准一致的方法;
  4. 元数据不仅可以用在对象上,也可以通过相关捕获器用在 Proxy 上;
  5. 对开发人员来说,定义新的元数据生成装饰器应该简洁

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)

  1. 通过装饰器声明方式创建,推荐的方式,也是很主流的一种方式(例子在上面基础用法)
  2. “事后”(类创建完后)再给目标对象创建元数据,代码如下
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 方法

image.pngimage.png

二、判断是否存在元数据(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())); 

输出:

image.png

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 

image.pngimage.png

如果你用 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

(0)
上一篇 2026年3月16日 下午8:46
下一篇 2026年3月16日 下午8:46


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号