最近,在排查项目上的一些权限问题,碰到了nest.js的一个API:"setMetaData" 。它是一个设置元数据(metadata)的装饰器。为了搞明白,设置后的元数据是在哪个地方。我便去翻了一下"setMetaData"的源码。看完源码后,便接触到了 元数据 和 元编程(metaprogramming)。
那么,什么是元数据?什么是元编程呢?
在日常生活中,元数据是无所不在的。我在阮一峰大神的博客下看到这么一句描述:"元数据是用来描述数据的数据(Data that describes other data)" 。我们可以通过生活中的场景来认识它。平时观看一部电影时,我们在看电影的简介时,而"简介"它就是元数据。 也可以用我们自身来描述元数据,每个人都有"姓名"、"年龄"、"身高"、"性格"等, 而这些就是元数据。
而在程序中,我们也可以把生活看成一个数据库。而姓名是我们数据库表里的"name"字段。而"name"它便是元数据。
做过网站SEO优化的同学都知道,HTML有一个文档级元数据元素,而keywords是经常被用于提高网站的SEO的元数据,搜索引擎会根据keywords关键词去归因。
"元编程能让你拥有可以扩展程序自身能力"。元编程可以让你修改程序的底层运行的规则,同时可以拓展和更好的维护程序。
ES6之后引入了一些常见的元编程有:Proxy、Reflect、Symbol等。在我们平时编程,常用Proxy结合Reflect去进行控制程序的运行规则。
如我们所见的Vue3.0通过结合Proxy和Reflect去代理对象和反射对象来实现响应式原理。看到这,相信大家对元数据和元编程有一定的理解了,接下来通过nest.js的源码展开。
以下是nest.js设置元数据的业务场景:
接下来我们从nest.js源码来分析setMetadata是怎么实现的。
采用Reflect.defineMetadata去定义一个元数据。而Reflect.defineMetadata 是 Reflect Metadata的方法。
Reflect Metadata是15年的一个提案,如果要用Reflect Metadata就要借助三方包(reflect-metadata)。我们需要去安装一下该npm包:
TS环境已经实现了@Reflect.metadata装饰器,我们通过创建index.ts文件编译一个例子。
用tsc去编译一下之后可以得到后的index.js,已经有了我们定义的元数据。
但是tsc编译选项可以通过开启emitDecoratorMetadata就可以自动添加一些元数据。因为Reflect.metadata还是草案阶段,所以还需要开启另一个选项experimentalDecorators。
编译后的结果多了三条元数据,分别是目标的类型、目标的参数类型、目标的返回值类型。
在nest.js的setMetadata的实现核心就是Reflect metadata的API。
setMetadata中的Reflect.defineMetadata就是定义一个元数据,元数据设置后是存在的目标上,也就是类,或者对象上。
设置元数据后的对象上都会存在 [[Metadata]]属性,该属性是一个 Map 对象。因为Reflect Metadata源码上采用的数据结构是WeakMap。主要是WeakMap不增加引用计数(垃圾回收的一种方式)的特点。
而更多的Reflect Metadata的方法可以直接看文档。
Metadata Proposal - ECMAScript
1. nest.js的setMetadata装饰器实现原理是Reflect Metadata。设置后的元数据是存在类或者对象上。
2. nest.js的Controller、Module、Service等一些装饰器都是通过Reflect Metadata给类和对象添加元数据,然后初始化时取出元数据做依赖扫描,实例化后放到IOC的容器里。
最后你会发现,实现nest.js的原理和元数据(metadata)、元编程(metaprogramming)有一腿。