Skip to content

reflect-metadata

原理分析

使用三层weakmap保存信息,对象、对象的属性、metakey。

ts
const a = {
  b: '123',
}

Reflect.defineMetadata('some key', 'some value', a, b)

全局元数据注册表 (内部 WeakMap): 这是最外层,存储了所有元数据。它是一个 WeakMap,以被装饰的目标对象(通常是类的构造函数或原型)作为键。使用 WeakMap 有助于垃圾回收,避免内存泄漏。

目标对象 / 类构造函数 (Target Object / Class Constructor): 这是第一层 Map 的值,它本身又是一个 Map。这个 Map 的键是属性键 (PropertyKey)。

属性键 (PropertyKey): 这是第二层 Map 的键。如果元数据是针对整个类定义的(例如,通过类装饰器),则属性键通常是 undefined。如果元数据是针对类的特定属性或方法定义的,则属性键是该属性或方法的名称(字符串或 Symbol)。对于参数的元数据,它通常也关联到方法上,但通过特定的元数据键(如 design:paramtypes)或参数索引来区分。

元数据键 (Metadata Key): 这是第二层 Map 的值,它本身是第三层 Map 的键。这个键用于唯一标识你存储的特定元数据项。

design:type: 由 emitDecoratorMetadata 编译器选项自动生成,表示属性或参数的类型。

design:paramtypes: 同样由 emitDecoratorMetadata 生成,表示方法的参数类型数组。

design:returntype: 表示方法的返回类型。

自定义键: 你可以使用 Symbol 或字符串来定义自己的元数据键,例如图中的 'myCustomData' 和 'isInjectable'。

元数据值 (Metadata Value): 这是最内层,存储了与特定元数据键关联的实际数据。它可以是任何 JavaScript 值(基本类型、对象、数组等)。

注意事项

  1. 使用过程中需要注意的是元数据到底定义在什么上面的, 特别是装饰器的场景下。
  2. 元数据都是以对象或者对象的属性来定义的。
  3. 原型是神坑,一定要注意

常用API

  • Reflect.getMetadataKeys 查询对象及对象原型链上所有的metadatakey(如果是对象属性,是原型上相同对象属性名的metadata)
  • Reflect.getOwnMedataKeys 仅查询该对象或者属性上的metadata

装饰器

class 装饰器

应用于构造函数观察、修改、替换,参数只有一个构造函数,如果返回一个值会替换构造函数

ts
function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    reportingURL = 'http://www...'
  }
}

@reportableClassDecorator
class BugReport {
  type = 'report'
  title: string

  constructor(t: string) {
    this.title = t
  }
}

方法装饰器

观察、修改、替换方法。传入三个参数实例的原型(如果是对静态方法,传入的是构造函数)、方法名和方法的属性描述符,如果返回一个值,将作为属性描述符。

ts
function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value
  }
}

class Greeter {
  greeting: string
  constructor(message: string) {
    this.greeting = message
  }

  @enumerable(false)
  greet() {
    return 'Hello, ' + this.greeting
  }
}

访问器装饰器

参数同方法装饰器,应用于属性描述符,用于观察修改替换访问器

属性装饰器

仅用于观察属性、因为ts中属性不会初始化,是不存在的变量。所以属性装饰器只能观察,比如定义元数据在后续对该变量做操作时使用。参数包括实例的原型对象(或者构造器),属性名

参数装饰器

参数包括实例的原型对象(构造器),方法名、参数索引位置。这个装饰器不能修改参数、需要配合方法装饰器使用,比如通过参数装饰器在方法上定义一个metadata,在方法装饰器中根据定义的元信息做一些操作

ts
import 'reflect-metadata'
const requiredMetadataKey = Symbol('required')

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  let existingRequiredParameters: number[] =
    Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || []
  existingRequiredParameters.push(parameterIndex)
  Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey)
}

function validate(
  target: any,
  propertyName: string,
  descriptor: TypedPropertyDescriptor<Function>,
) {
  let method = descriptor.value!

  descriptor.value = function () {
    let requiredParameters: number[] = Reflect.getOwnMetadata(
      requiredMetadataKey,
      target,
      propertyName,
    )
    if (requiredParameters) {
      for (let parameterIndex of requiredParameters) {
        if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
          throw new Error('Missing required argument.')
        }
      }
    }
    return method.apply(this, arguments)
  }
}

class BugReport {
  type = 'report'
  title: string

  constructor(t: string) {
    this.title = t
  }

  @validate
  print(@required verbose: boolean) {
    if (verbose) {
      return `type: ${this.type}\ntitle: ${this.title}`
    } else {
      return this.title
    }
  }
}

装饰器执行顺序

分为表达式的执行步骤和装饰器执行步骤。

  • 对于表达式,不同类从上到下执行。同类先执行 属性 | 方法 => 类,其中属性和方法谁申明在前面谁先执行,如果有参数、紧跟着方法装饰器后评估

  • 对于装饰器总是按照 属性 | 方法 => 类,其中属性和方法谁先申明谁先执行,方法装饰器和参数装饰器,参数装饰器优先、参数装饰器中排后的优先

一定要联想转换后的代码、执行顺序很重要

装饰器的pollyfill 来理解装饰器

例子

神奇的源码

ts
function ClassDecorator() {
  console.log('ClassDecorator 1')
  return function (constructor) {
    console.log('ClassDecorator 2')
  }
}

function PropertyDecorator() {
  console.log('PropertyDecorator 1')
  return function (constructor, key) {
    console.log('PropertyDecorator 2')
  }
}

function MethodDecorator() {
  console.log('MethodDecorator 1')
  return function (constructor, key) {
    console.log('MethodDecorator 2')
  }
}

function ParamDecorator() {
  console.log('ParamDecorator 1')
  return function (c, k, i) {
    console.log('ParamDecorator 2')
  }
}

@ClassDecorator()
class Demo {
  @PropertyDecorator()
  d1

  @MethodDecorator()
  test(@ParamDecorator() a) {
    // 错误点2
  }
}

console.log('before') // 错误点1
const test = new Demo()
console.log('end')

装饰器pollyfill

js
var __decorate =
  (this && this.__decorate) ||
  function (decorators, target, key, descriptor) {
    var argLength = arguments.length
    // 通过参数判断当前是什么类型的装饰器 c < 3 类装饰器, C === 3 属性装饰器 / 参数装饰器(详情看__param) C > 3 方法装饰器
    var r =
      argLength < 3
        ? target
        : descriptor === null
          ? (descriptor = Object.getOwnPropertyDescriptor(target, key))
          : descriptor
    // 类装饰器 r = target  方法装饰器 |  参数装饰器  r = descriptor  属性装饰器 = undefined

    if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')
      // 如果有原生Reflect.decorate
      r = Reflect.decorate(decorators, target, key, descriptor)
    else
      for (
        var i = decorators.length - 1;
        i >= 0;
        i-- // @A @B => A(B()) 反向
      ) {
        var decorator = decorators[i]
        if (decorator)
          r =
            (argLength < 3
              ? decorator(r)
              : argLength > 3
                ? decorator(target, key, r)
                : decorator(target, key)) || r

        // 类装饰器 r = decorators[i](r) || r,  r 初始值等于target  相当于一直使用装饰器修改target。for循环后 r 是最终修改的后target
        //  属性装饰器 r = decorator(target, key)) || r,因为r最初就是undefind,大部分场景下decorator(target, key) 不会返回值, 如果返回r就等于返回值,但是在最后 r 也会被舍弃,没有被Object.defineProperty
        // 方法装饰器 r初始值是对象 r = decorator(target, key, r) || r,不管是返回还是直接修改都会得到一个描述符, 最后把描述符重新设置给target,
        // 参数装饰器有两个逻辑,会在方法执行器中执行,并且不返回r,其次参数装饰器会自传入3参数执行。
      }

    return (argLength > 3 && r && Object.defineProperty(target, key, r), r) // 返回的是r, 前面相当于if
  }

var __metadata =
  (this && this.__metadata) ||
  function (k, v) {
    if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function')
      return Reflect.metadata(k, v) // 返回的函数会根据参数来判断是给class装饰 还是给方法或者属性装饰
  }
var __param =
  (this && this.__param) ||
  function (paramIndex, decorator) {
    return function (target, key) {
      decorator(target, key, paramIndex)
    }
  }

执行顺序和入参

js
function ClassDecorator() {
  console.log('ClassDecorator 1')
  return function (constructor) {
    console.log('ClassDecorator 2')
  }
}
function PropertyDecorator() {
  console.log('PropertyDecorator 1')
  return function (constructor, key) {
    console.log('PropertyDecorator 2')
  }
}
function MethodDecorator() {
  console.log('MethodDecorator 1')
  return function (constructor, key) {
    console.log('MethodDecorator 2')
  }
}
function ParamDecorator() {
  console.log('ParamDecorator 1')
  return function (c, k, i) {
    console.log('ParamDecorator 2')
  }
}
var Demo = /** @class */ (function () {
  function Demo() {}
  Demo.prototype.test = function (a) {}
  __decorate([PropertyDecorator(), __metadata('design:type', Object)], Demo.prototype, 'd1', void 0)
  __decorate(
    [
      MethodDecorator(),
      __param(0, ParamDecorator()),
      __metadata('design:type', Function),
      __metadata('design:paramtypes', [Object]),
      __metadata('design:returntype', void 0),
    ],
    Demo.prototype,
    'test',
    null,
  )
  Demo = __decorate([ClassDecorator()], Demo)
  return Demo
})()
console.log('before')
var test = new Demo()
console.log('end')