reflect-metadata 是一个为 JavaScript/TypeScript 提供元数据反射 API 的库,主要解决以下问题:
- 元数据存储与读取:在装饰器模式中,需要为类、方法、属性附加额外的信息(如类型、配置、依赖关系等),但原生 JavaScript 缺乏标准化的元数据存储机制。
- 框架依赖管理:支持依赖注入(DI)框架(如 Angular、NestJS)自动解析类依赖,通过元数据获取构造函数参数类型。
- 装饰器增强:使装饰器能够声明式地附加元数据(如路由信息、验证规则),供运行时动态读取。
本文记录 Reflect-metadata 实现原理 、 TypeScript 装饰器源码实现 。
Reflect-metadata 实现原理
reflect-metadata 的核心目标是为 JavaScript/TypeScript 提供元数据存储和反射能力。其实现原理基于以下关键机制:
1. 元数据存储模型
reflect-metadata 使用 分层存储结构 来管理元数据,核心数据结构是 WeakMap,确保对象被回收时元数据自动释放,避免内存泄漏。
存储层级:
- Metadata Key:元数据的唯一标识(如字符串或
Symbol)。 - Target Object:元数据关联的目标(类、原型、实例等)。
- Property Key:可选属性或方法名(如类属性、方法)。
存储结构如下:
// 伪代码表示
const MetadataStorage = new WeakMap<Object, Map<string | Symbol, Map<string | undefined, any>>>();
- 外层
WeakMap:键为 目标对象(如类构造函数),值为该对象的所有元数据。 - 中层
Map:键为 元数据键(如'design:type'),值为具体属性或方法的元数据。 - 内层
Map:键为 属性名(如'myMethod'),值为实际存储的元数据值。
存储示例:
@Reflect.metadata('key', 'value')
class MyClass {
@Reflect.metadata('prop', 123)
myProp: string;
}
存储结构伪代码:
MetadataStorage = {
[MyClass]: {
'key': {
undefined: 'value' // 类级别的元数据(无属性名)
},
'prop': {
'myProp': 123 // 属性级别的元数据
}
}
};
2. 元数据读写流程
2.1 写入元数据
通过 Reflect.defineMetadata 或装饰器 @Reflect.metadata 写入:
const metadataStore = new WeakMap();
function defineMetadata(metadataKey, metadataValue, target, propertyKey) {
let targetMetadata = metadataStore.get(target);
if (!targetMetadata) {
targetMetadata = new Map();
metadataStore.set(target, targetMetadata);
}
let propertyMetadata;
if (propertyKey) {
propertyMetadata = targetMetadata.get(propertyKey);
if (!propertyMetadata) {
propertyMetadata = new Map();
targetMetadata.set(propertyKey, propertyMetadata);
}
} else {
propertyMetadata = targetMetadata;
}
propertyMetadata.set(metadataKey, metadataValue);
}
2.2 读取元数据
通过 Reflect.getMetadata 读取:
function getMetadata(metadataKey, target, propertyKey) {
const targetMetadata = metadataStore.get(target);
if (!targetMetadata) {
return undefined;
}
let propertyMetadata;
if (propertyKey) {
propertyMetadata = targetMetadata.get(propertyKey);
if (!propertyMetadata) {
return undefined;
}
} else {
propertyMetadata = targetMetadata;
}
return propertyMetadata.get(metadataKey);
}
3. TypeScript 元数据自动生成
当开启 emitDecoratorMetadata 时,TypeScript 编译器会在 编译阶段 自动为装饰器修饰的目标生成以下元数据:
3.1 生成规则
design:type:被装饰目标的类型。- 类属性 → 属性类型(如
String、Number)。 - 方法 →
Function。
- 类属性 → 属性类型(如
design:paramtypes:方法或构造函数的参数类型数组。design:returntype:方法的返回值类型。
3.2 实现机制
TypeScript 编译器在编译时注入代码,例如:
class MyClass {
@Decorator
method(param: string): number {
return 42;
}
}
编译后(简化):
// 自动注入类型元数据
Reflect.defineMetadata("design:paramtypes", [String], MyClass.prototype, "method");
Reflect.defineMetadata("design:returntype", Number, MyClass.prototype, "method");
4. 原型链与继承处理
reflect-metadata 支持通过原型链查找元数据,例如:
class Parent {
@Reflect.metadata('key', 'parent-value')
method() {}
}
class Child extends Parent {
method() {}
}
// 读取 Child 的 method 元数据时,会沿原型链查找 Parent
const value = Reflect.getMetadata('key', Child.prototype, 'method'); // 'parent-value'
实现细节:
- 当目标对象不存在元数据时,递归查找其原型链。
- 使用
Object.getPrototypeOf()遍历原型链,直到找到元数据或到达null。
5. 性能优化策略
reflect-metadata 通过以下方式优化性能:
WeakMap弱引用:目标对象被销毁时,自动释放其元数据,避免内存泄漏。- 惰性初始化:仅在首次写入元数据时创建存储结构,减少内存占用。
- 原型链缓存:在查找元数据时缓存遍历结果,减少重复计算。
6. 核心 API 实现
Reflect.metadata 装饰器工厂:
function metadata(key: any, value: any) {
return (target: any, propertyKey?: string) => {
Reflect.defineMetadata(key, value, target, propertyKey);
};
}
Reflect.getOwnMetadata:
直接读取目标自身的元数据,不查找原型链:
function getOwnMetadata(key, target, propertyKey) {
const targetMetadata = MetadataStorage.get(target);
// ... 仅读取当前目标的元数据
}
7. 边界条件处理
- 非对象目标:若目标不是对象(如原始类型
number),抛出错误。 - Symbol 键处理:支持
Symbol作为元数据键,避免命名冲突。 - 默认元数据:若未找到元数据,返回
undefined。
总结
reflect-metadata 的实现核心在于:
- 使用
WeakMap实现高效、安全的元数据存储。 - 与 TypeScript 编译器深度集成,自动生成类型元数据。
- 支持原型链查找和继承逻辑。
- 通过装饰器模式和反射 API 提供声明式元数据操作。
其设计平衡了灵活性、性能与内存安全,成为现代 JavaScript/TypeScript 生态中元编程的基石。接下来是TypeScript 中装饰器的实现思路以及装饰器代码执行时机。