Appearance
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 值(基本类型、对象、数组等)。
注意事项
- 使用过程中需要注意的是元数据到底定义在什么上面的, 特别是装饰器的场景下。
- 元数据都是以对象或者对象的属性来定义的。
- 原型是神坑,一定要注意
常用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')