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?): booleanhasOwnMetadata(k, o, p?): boolean// 获取元数据getMetadata(k, o, p?): anygetOwnMetadata(k, o, p?): any// 获取所有元数据的 KeygetMetadataKeys(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 方法

二、判断是否存在元数据(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);// 筛选出类的 methodNameconst methodsNames = Object.getOwnPropertyNames(prototype).filter(item => !isConstructor(item) && isFunction(prototype[item]))return methodsNames.map(methodName => {const fn = prototype[methodName];// 取出定义的 metadataconst 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

es7之Reflect Metadata相关推荐

  1. 【Angular专题】 (3)装饰器decorator,一块语法糖

    一. Decorator装饰器 修饰器是ES7加入的新特性,Angular中进行了大量使用,有很多内置的修饰器,后端的同学一般称之为"注解".修饰器的作用,实际上就是设计模式中常说 ...

  2. 手把手教你实现TypeScript下的IoC容器

    在此篇文章开始之前,先向大家简单介绍 IoC.什么是 IoC?以及为什么我们需要 IoC?以及本文核心,在 TypeScript 中实现一个简单的 IoC 容器? 目录 [隐藏] IoC 定义 初识 ...

  3. Nest.js中的设计模式——装饰器

    背景 Nest是围绕一种称为装饰器的语言特性构建的. 装饰器风格的实现 Nest在面向对象设计中用到了装饰器模式去组织代码. 装饰器模式是一种动态地往一个类别中添加新的行为的设计模式 在写项目的时候, ...

  4. 【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--请求处理函数装饰器注册篇(5/8)【controller+action】...

    文章目录 前情概要 上篇文章把action的注册讲完了,但是我们的处理函数没有指定可接受的httpmethod,也没有别名上面的.下面我们使用typescript的特性之一装饰器来实现一把这个特性. ...

  5. TypeScript技术知识整理

    TypeScript技术知识整理 文章目录 TypeScript技术知识整理 一.环境搭建与编译执行 1.安装 `TypeScript` 编译器 2.编写代码 **代码编辑器 - vscode** 3 ...

  6. arcgis js 4 风场可视化

    当我们做洋流或者风场 可视化时候 echart 虽然也能用 但是数据量过大会很卡 数据调用是这个样子 样例数据 链接: https://pan.baidu.com/s/1yQrIMBMJdSPwnnI ...

  7. ReactNative进阶(三十一): IoC 框架 InversifyJS 解读

    文章目录 一.简介 二.InversifyJS 为何而生 ? 三.目标 四.安装 五.应用示例 步骤 1: 声明接口和类型 步骤 2: 使用 @injectable 和 @inject 装饰器声明依赖 ...

  8. Eclipse Theia 揭秘之启动流程篇

    前言 在<Eclipse Theia 框架技术架构揭秘>一文中简单介绍了 Theia 框架整体的技术架构,接下来将通过系列文章从源码角度入手看一下 Theia 核心设计思路,本文从启动流程 ...

  9. Vue Injector组件库易于维护和例行测试

    Vue Injector组件库易于维护和例行测试 Vue Injector 是一个组件库,专门设计用于帮助用户在 Vue.js 环境中工作时执行依赖注入.包含一系列专用实例,它将使人们能够实现依赖注入 ...

  10. babel源码解析之(@babel/preset-env)

    前言 还记得之前写过一篇文章:babel源码解析一,里面把babel的整个流程跑了一遍,最后还自定义了一个插件用来转换"箭头函数",通过前面的源码解析我们知道,preset其实就是 ...

最新文章

  1. POJ 3620-Avoid The Lakes
  2. boost::signals2模块实现为类定义预析构函数的最小示例的测试程序
  3. 经典C语言程序100例之二七
  4. Perl 面对对象的案例理解
  5. lru算法实现 redis_使用数组与双向链表实现一个简单的LRU算法
  6. 排序算法三:堆排序基本原理以及Python实现
  7. mysql max datetime_MYSQL在联接语句中选择MAX日期
  8. C++ 右值引用 | 左值、右值、move、移动语义、引用限定符
  9. SpringBoot 扫描包
  10. php getfullyear,getYear、getFullYear和getUTCFullYear三者的区别
  11. 手机通讯录备份代码实现二
  12. 超码、候选码、主码 与 外码
  13. pytest文档24-fixture的作用范围(scope)
  14. matlab里的pid参数调节,基于MATLAB的PID控制系统参数调节
  15. 机器学习-泰坦尼克号幸存者预测
  16. 西南大学2019春计算机作业答案,2019年西南大学作业答案[1175]《仪器分析》
  17. 自学 Photoshop 2022 Mac版-笔记1
  18. chatgpt为什么在中国不能被使用
  19. 大型的obj文件如何处理和加载
  20. 局域网如何共享文件?计算机1通过ip访问计算机2,拿取共享文件。

热门文章

  1. 精益创业实战 - 第5章 开始实验
  2. 不动产租赁运营平台,为不动产租赁提供强劲的运营支持
  3. BERT: Pre-training of Deep Bidirectional Transformers forLanguage Understanding
  4. MPB:山大倪金凤组-​​白蚁肠道微生物样品收集与制备
  5. win10系统下计算器界面变成英文的解决方法
  6. C# 实现截图软件功能
  7. 项目经理:什么是矩阵型组织结构?
  8. 图片格式转换工具(ReaConverter Pro)中文免费版 v7.441
  9. win7忘记密码解决,Administrator账号密码忘记 解决办法
  10. 【vue】imitate-beautiful-thing