1. 什么是元数据(MetaData)

元数据是用来描述数据的数据(Data that describes other data)

2. reflect-matadata

2.1 目的

  • 很多设计模式 比如组合 依赖注入 运行时类型断言,反射/镜像 测试等希望可以在保持原有class的一致性的前提下为class添加元数据。
  • 一致性是很多工具和库使用元数据的原因
  • 元数据产生的装饰器可以通过改变装饰器来进行组合
  • 元数据不仅仅只能在对象上使用 也应该被代理Proxy通过相应的 traps所使用
  • 元数据应该保持与其他语言以及 ECMAScript运行特性的一致性

    2.2 API解读

    2.2.1 decorate

    第一个方法就是decorate 有好几个重载,一个一个来看

2.2.1.1 对类的装饰

  1. /**
  2. * 应用于一个装饰器的集合给目标对象
  3. * @param decorators 装饰器的数组
  4. * @param target 目标对象
  5. * @returns 返回应用提供的装饰器后的值
  6. * 注意: 装饰器应用是与array的位置方向相反, 为从右往左
  7. */
  8. function decorate(decorators: ClassDecorator[], target: Function): Function;

那么这个 ClassDecorator 究竟是个什么数组呢?

  1. declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

该定义为: 这是一个Function, 只有一个参数Target, 也就是类的构造函数constructor, 返回值的类型与target的函数类型一致或为空, 也就是说, 如果是一个类的话, 续需返回这个类或者空. 注意, 该类型的定义不在reflect-matadata中, 而是在lib.es5.d.ts中, 也就表明该为es5的原生实现

举个例子

  1. const classDecorator: ClassDecorator = target => {
  2. target.prototype.sayName = () => console.log('override');
  3. // return target 这里可以return也可以不return, 因为target是一个对象引用
  4. }
  5. export class TestClassDecrator {
  6. constructor(public name = '') {
  7. }
  8. sayName() {
  9. console.log(this.name)
  10. }
  11. }
  12. Reflect.decorate([classDecorator], TestClassDecrator) // 对其进行装饰
  13. const t = new TestClassDecrator('nihao')
  14. t.sayName() // 'override'

注意: 在classDecorator中传入的target, 只能修改其prototype的方法, 不能修改其属性, 因为其属性是 read-only

2.2.1.2 对属性或者方法的装饰

  1. function decorate(
  2. decorators: (PropertyDecorator | MethodDecorator)[],
  3. target: Object,
  4. propertyKey: string | symbol,
  5. attributes?: PropertyDescriptor
  6. ): PropertyDescriptor

顺便提一嘴, descriptor 分为两种, 一种是数据描述符, 一种是存取描述符

  1. // 数据描述符
  2. {
  3. value:&emsp;'aaa',
  4. configurable: true,
  5. writable: true,
  6. enumerable: true
  7. }
  8. // 存取描述符
  9. {
  10. get(){return 1},
  11. set() {console.log('set')},
  12. configurable: true,
  13. enumerable: true
  14. }

回到方法和属性装饰器 举例子

属性装饰器

  1. const propertyDecorator: PropertyDecorator = (target, propertyKey) => {
  2. const origin = target[propertyKey]
  3. target[propertyKey] = () => {
  4. origin.call(target)
  5. console.log('added override')
  6. }
  7. }
  8. class PropertyAndMethodExample {
  9. static staticProperty() {
  10. console.log('im static property')
  11. }
  12. method() {
  13. console.log('im one instance method')
  14. }
  15. }
  16. Reflect.decorate([propertyDecorator], PropertyAndMethodExample, 'staticProperty')
  17. // test property decorator
  18. PropertyAndMethodExample.staticProperty() // im static property \n added override

方法装饰器

  1. const methodDecorator:MethodDecorator = (target, propertyKey, descriptor) => {
  2. // 将其描述改为不可编辑
  3. descriptor.configurable = false
  4. descriptor.writable = false
  5. return descriptor
  6. }
  7. // 获取原descriptor
  8. let descriptor = Object.getOwnPropertyDescriptor(PropertyAndMethodExample.prototype, 'method')
  9. // 获取修改后的descriptor
  10. descriptor = Reflect.decorate([methodDecorator], PropertyAndMethodExample, 'method', descriptor)
  11. // 将修改后的descriptor添加到对应的方法上
  12. Object.defineProperty(PropertyAndMethodExample.prototype, 'method', descriptor)
  13. // test method decorator
  14. const example = new PropertyAndMethodExample
  15. example.method = () => console.log('override') // 报错 Cannot assign to read only property 'method' of object '#<PropertyAndMethodExample>'

2.2.2 metadata

默认的元数据装饰器可以被用于类 类成员以及参数

注意: 如果 metadataKey 已经被定义在 target 或 target key 那么 metadataValue将会被覆盖

  1. /**
  2. * @param metadataKey 元数据入口的key
  3. * @param metadataValue 元数据入口的value
  4. * @returns: 装饰器函数
  5. */
  6. function metadata(metadataKey: any, metadataValue: any): {
  7. (target: Function): void;
  8. (target: Object, properKey: string | Symbol): void
  9. }

下面就使用一波

  1. const nameSymbol = Symbol('lorry')
  2. // 类元数据
  3. @Reflect.metadata('class', 'class')
  4. class MetaDataClass {
  5. // 实例属性元数据
  6. @Reflect.metadata(nameSymbol, 'nihao')
  7. public name = 'origin'
  8. // 实例方法元数据
  9. @Reflect.metadata('getName', 'getName')
  10. public getName() {
  11. }
  12. // 静态方法元数据
  13. @Reflect.metadata('static', 'static')
  14. static staticMethod () {
  15. }
  16. }
  17. const value = Reflect.getMetadata('name', MetaDataClass)
  18. const metadataInstance = new MetaDataClass
  19. const name = Reflect.getMetadata(nameSymbol, metadataInstance, 'name')
  20. const methodVal = Reflect.getMetadata('getName', metadataInstance, 'getName')
  21. const staticVal = Reflect.getMetadata('static', MetaDataClass, 'staticMethod')
  22. console.log(value,name,methodVal,staticVal)
  23. // undefined nihao getName static

2.2.3 defineMetadata

该方法是 metadata 的定义版本 也就是非@版本 会多传一个参数 target 表示待装饰的对象
ts定义如下

  1. /**
  2. * @param metadataKey 设置或获取时的key
  3. * @param metadataValue 元数据内容
  4. * @param target 待装饰的target
  5. * @param targetKey target的property
  6. */
  7. function defineMetadata(metadataKey: any, metadataValue: any, target: Object, targetKey: string | symbol) :void

查看例子

  1. class DefineMetadata {
  2. static staticMethod () {}
  3. static staticProperty = 'static'
  4. getName() {}
  5. }
  6. const type = 'type'
  7. Reflect.defineMetadata(type, 'class', DefineMetadata)
  8. Reflect.defineMetadata(type, 'staticMethod', DefineMetadata.staticMethod)
  9. Reflect.defineMetadata(type, 'method', DefineMetadata.prototype.getName)
  10. Reflect.defineMetadata(type, 'staticProperty', DefineMetadata, 'staticProperty')
  11. const t1 = Reflect.getMetadata(type, DefineMetadata)
  12. const t2 = Reflect.getMetadata(type, DefineMetadata.staticMethod)
  13. const t3 = Reflect.getMetadata(type, DefineMetadata.prototype.getName)
  14. const t4 = Reflect.getMetadata(type, DefineMetadata,'staticProperty')
  15. console.log(t1,t2,t3,t4) // class staticMethod method staticProperty

注意 t4定义和获取不一样的地方 比如 t2和t3都有两种写法, 一种是将target传为对应的对象且必须为对象 以t2为例 也可以写为

  1. Reflect.defineMetadata(type, 'staticMethos', DefineMetadata, 'staticMethod')
  2. const t2 = Reflect.getMetadata(type, DefineMetadata, 'staticMethod')

需要注意的是这两者并不能混合使用, 比如

  1. Reflect.defineMetadata(type, 'staticMethos', DefineMetadata, 'staticMethod')
  2. const t2 = Reflect.getMetadata(type, DefineMetadata.staticMethod)

是无法获取到对应的metadataValue的 原因是如果未传入property 会当 property当作undefined,此时该target的 metadata entries(本质是一个Map)中就有一个 {undefined: Map()}而传入了 property 就是在该 target下的propery属性下的 entries set一个 {propery: Map()} 具体拿t2来说 方法1

  1. Reflect.defineMetadata(type, 'staticMethod', DefineMetadata.staticMethod)

这条语句是在DefineMetadata.staticMethod 下的元数据为
为啥呢????
image.png

方法2

  1. Reflect.defineMetadata(type, 'staticMethos', DefineMetadata, 'staticMethod')

image.png
明显这两者不等价, 所以无法互换. 顺带一嘴, @Reflect.metadata的行为跟设置property一致, 也就是为方法2的实现, 使用方法1获取@Reflect.metadata的数据会为undefined

2.2.4 hasMetaData

该方法返回布尔值 表明该target或其原型链上有没有对应的元数据

  1. /**
  2. * @param metadataKey 元数据的key
  3. * @param target 定义的对象
  4. * @param targetKey 重载参数, 可选
  5. * @returns 在target或其原型链上返回true.
  6. */
  7. function hasMetadata(metadataKey: any, target: Object, targetKey?: symbol|string): boolean;

举个例子

  1. const type = 'type'
  2. class HasMetadataClass {
  3. @Reflect.metadata(type, 'staticProperty')
  4. static staticProperty = ''
  5. }
  6. Reflect.defineMetadata(type, 'class', HasMetadataClass)
  7. const t1 = Reflect.hasMetadata(type, HasMetadataClass)
  8. const t2 = Reflect.hasMetadata(type, HasMetadataClass, 'staticProperty')
  9. console.log(t1, t2)

2.2.5 hasOwnMetadata

跟Object.prototype.hasOwnProperty类似, 是只查找对象上的元数据, 而不会继续向上查找原型链上的, 其余的跟hasMetadata一致

  1. const type = 'type'
  2. class Parent {
  3. @Reflect.metadata(type, 'getName')
  4. getName() {}
  5. }
  6. @Reflect.metadata(type, 'class')
  7. class HasOwnMetadataClass extends Parent{
  8. @Reflect.metadata(type, 'static')
  9. static staticProperty() {}
  10. @Reflect.metadata(type, 'method')
  11. method() {}
  12. }
  13. const t1 = Reflect.hasOwnMetadata(type, HasOwnMetadataClass)
  14. const t2 = Reflect.hasOwnMetadata(type, HasOwnMetadataClass, 'staticProperty')
  15. const t3 = Reflect.hasOwnMetadata(type, HasOwnMetadataClass.prototype, 'method')
  16. const t4 = Reflect.hasOwnMetadata(type, HasOwnMetadataClass.prototype, 'getName')
  17. const t5 = Reflect.hasMetadata(type, HasOwnMetadataClass.prototype, 'getName')
  18. console.log(t1, t2, t3, t4, t5) // true true true false true
  19. // 注意t4和t5的区别

2.2.6 getMetadata

这个属性在之前验证各个属性的时候就已经使用过了, 就是用于获取target的元数据值, 会往原型链上找

  1. /**
  2. * @param metadataKey 元数据key
  3. * @param target 元数据定义的target
  4. * @param targetKey 可选项, 是否选择target的某个key
  5. * @returns 如果找到了元数据则返回元数据值, 否则返回undefined
  6. *
  7. */
  8. function getMetadata(metadataKey: any, target: Object, targetKey?: string | symbol ): any;

2.2.7 getOwnMetadata

与hasOwnMetadata和hasMetadata的区别一样, 是否往原型链上找

2.2.8 getMetadataKeys

类似Object.keys, 返回该target以及原型链上target的所有元数据的keys

  1. const type = 'type'
  2. @Reflect.metadata('parent', 'parent')
  3. class Parent {
  4. getName() {}
  5. }
  6. @Reflect.metadata(type, 'class')
  7. class HasOwnMetadataClass extends Parent{
  8. @Reflect.metadata(type, 'static')
  9. static staticProperty() {}
  10. @Reflect.metadata('bbb', 'method')
  11. @Reflect.metadata('aaa', 'method')
  12. method() {}
  13. }
  14. const t1 = Reflect.getMetadataKeys(HasOwnMetadataClass)
  15. const t2 = Reflect.getMetadataKeys(HasOwnMetadataClass.prototype, 'method')
  16. console.log(t1, t2) // ["type", "parent"] \n ["design:returntype", "design:paramtypes", "design:type", "aaa", "bbb"]

t1很好理解, 因为会向上找原型链的parent t2好像多了一些东西, design: 开头的, 先按下不表, 看看 ‘aaa’ 和 ‘bbb’ 的顺序是和我们添加的顺序是相反的, 还记得前面说过装饰器的顺序是从右到左的, 所以先应用的bbb, aaa再应用的design:xxx

2.2.9 getOwnMetadataKeys

跟getMetadataKeys 一样, 只是不向原型链中查找

2.2.10 deleteMetadata

用于删除元数据

  1. /**
  2. * @param metadataKey 元数据key
  3. * @param target 元数据定义的对象
  4. * @param targetKey 对象对应的key
  5. * @returns 如果对象上有该元数据, 返回true, 否则返回false
  6. */
  7. function deleteMetadata(metadataKey: any, target: Object, targetKey?:symbol|string): boolean;
  1. const type = 'type'
  2. @Reflect.metadata(type, 'class')
  3. class DeleteMetadata {
  4. @Reflect.metadata(type, 'static')
  5. static staticMethod() {}
  6. }
  7. const res1 = Reflect.deleteMetadata(type, DeleteMetadata)
  8. const res2 = Reflect.deleteMetadata(type, DeleteMetadata, 'staticMethod')
  9. const res3 = Reflect.deleteMetadata(type, DeleteMetadata)
  10. console.log(res1, res2, res3) // true true false

2.2.11 design:

还有一个问题 没有解决 就是之前说的在 getMetadataKey时出现的 design:xxx的内容时怎么来的 表示什么意思呢?

design:type 表示被装饰的对象是什么类型, 比如是字符串? 数字?还是函数等
design:paramtypes 表示被装饰对象的参数类型, 是一个表示类型的数组, 如果不是函数, 则没有该key
design:returntype 表示被装饰对象的返回值属性, 比如字符串,数字或函数等 来个例子

3. 参考链接

reflect-metadata的研究