装饰器是 TypeScript 中实现元编程的核心机制,通过它可以在不修改类或类成员原始定义的情况下,添加元数据、扩展功能或修改行为。本节将深入探讨装饰器的高级应用场景,包括类装饰器、属性装饰器的实现方式,装饰器的执行顺序,新旧版本装饰器的差异对比,以及通过实战案例与练习巩固知识点。
类装饰器是应用于类声明的装饰器,其核心作用是接收类的构造函数作为参数,从而实现类的注册、元数据添加或行为修改。典型应用场景包括服务注册、ORM 模型标记、依赖注入等。
基础示例:实现一个简单的类注册装饰器,在类定义时自动输出注册信息:
// 类装饰器:接收构造函数作为参数
function register(constructor: Function) {
console.log(`Registered class: ${constructor.name}`);
// 可通过构造函数原型添加元数据
constructor.prototype.isRegistered = true;
}
@register // 应用装饰器
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 输出:Registered class: User
console.log(User.prototype.isRegistered); // 输出:true
类装饰器的核心价值在于元数据管理,通过装饰器可以为类附加额外信息(如数据库表名、服务标识等),这些信息可在运行时通过反射机制读取,为框架或工具提供配置依据。
属性装饰器应用于类的属性声明,用于监控属性定义、添加字段元数据或修改属性行为。其接收两个参数:target(类的原型对象)和 propertyKey(属性名),在属性定义阶段执行。
基础示例:实现一个属性日志装饰器,在属性定义时输出字段信息:
// 属性装饰器:接收原型对象和属性名
function logProperty(target: any, propertyKey: string) {
console.log(`Property declared: ${propertyKey} (on ${target.constructor.name})`);
// 为属性添加元数据(可通过 Reflect API 存储)
Reflect.defineMetadata('logProperty', true, target, propertyKey);
}
class User {
@logProperty // 应用属性装饰器
name: string;
@logProperty
age: number;
}
// 输出:
// Property declared: name (on User)
// Property declared: age (on User)
属性装饰器常与类装饰器配合使用,例如在 ORM 框架中,类装饰器标记表名,属性装饰器标记字段类型和约束。
当多个装饰器应用于同一目标(类、方法或属性)时,其执行顺序遵循从下到上(或从右到左)的规则,即靠近目标的装饰器先执行。这一机制类似于函数组合,允许装饰器按特定顺序链式处理目标。
类装饰器顺序示例:
function decorator1(constructor: Function) {
console.log('decorator1 executed');
}
function decorator2(constructor: Function) {
console.log('decorator2 executed');
}
@decorator1 // 上层装饰器
@decorator2 // 下层装饰器(靠近类)
class A {}
// 输出顺序:decorator2 executed → decorator1 executed
方法装饰器顺序示例:
function bound(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('bound executed');
return descriptor;
}
function loggedMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('loggedMethod executed');
return descriptor;
}
class Greeter {
@bound // 下层装饰器(靠近方法)
@loggedMethod // 上层装饰器
greet() {}
}
// 输出顺序:bound executed → loggedMethod executed
关键规则:装饰器执行顺序为「从下到上」,即后声明的装饰器(靠近目标)先执行。在实际开发中,需注意装饰器依赖关系,例如依赖注入装饰器应优先于日志装饰器执行。
TypeScript 装饰器经历了从实验性特性(experimentalDecorators)到 TC39 标准语法的演进,两者在功能支持和语法规范上存在显著差异:
| 特性 | 旧版(experimentalDecorators) | 新版(标准装饰器) |
|---|---|---|
| 语法规范 | 非标准实验性语法 | 符合 TC39 装饰器提案(Stage 3) |
| 参数装饰器 | 支持(parameterDecorator) |
暂不支持(需通过其他方式实现参数处理) |
| 元数据发射 | 支持 emitDecoratorMetadata 选项 |
不支持自动发射元数据(需手动实现) |
| 装饰器返回值 | 类装饰器可返回新构造函数替换原类 | 所有装饰器返回值均被忽略 |
| 执行时机 | 类定义阶段执行 | 类初始化阶段执行(更接近运行时) |
| 兼容性 | 需显式开启 experimentalDecorators 选项 |
TypeScript 5.0+ 默认支持(需配置 target: ES2022+) |
迁移注意事项:若从旧版装饰器迁移至新版,需移除参数装饰器依赖,元数据管理需通过 Reflect.metadata 手动实现,且装饰器不再支持替换类构造函数。
依赖注入(DI)是装饰器的经典应用场景,通过类装饰器注册服务,属性装饰器自动注入依赖,实现组件解耦。以下是一个简化的 DI 容器实现:
// 1. 定义服务注册表
const serviceRegistry = new Map<string, any>();
// 2. 类装饰器:注册服务
function Injectable(identifier: string) {
return (constructor: Function) => {
serviceRegistry.set(identifier, constructor);
};
}
// 3. 属性装饰器:注入依赖
function Inject(identifier: string) {
return (target: any, propertyKey: string) => {
// 在类实例化时注入依赖
Object.defineProperty(target, propertyKey, {
get() {
const ServiceClass = serviceRegistry.get(identifier);
return new ServiceClass();
}
});
};
}
// 4. 使用示例
@Injectable('logger') // 注册日志服务
class LoggerService {
log(message: string) {
console.log(`[Log]: ${message}`);
}
}
class UserService {
@Inject('logger') // 注入日志服务
private logger!: LoggerService;
createUser() {
this.logger.log('User created'); // 调用注入的服务
}
}
// 测试
const userService = new UserService();
userService.createUser(); // 输出:[Log]: User created
目标:编写类装饰器 @Table 和属性装饰器 @Column,为 ORM 模型类添加表名和字段元数据,以便后续生成数据库表结构。
步骤:
@Table(tableName: string):为类添加表名元数据,存储在类的 tableMetadata 属性中。@Column(type: string):为属性添加字段类型元数据,存储在类的 columnMetadata 对象中(键为属性名,值为字段类型)。参考实现:
// 类装饰器:添加表名元数据
function Table(tableName: string) {
return (constructor: Function) => {
constructor.prototype.tableMetadata = { tableName };
};
}
// 属性装饰器:添加字段元数据
function Column(type: string) {
return (target: any, propertyKey: string) => {
if (!target.columnMetadata) {
target.columnMetadata = {};
}
target.columnMetadata[propertyKey] = { type };
};
}
// 使用示例
@Table('users')
class User {
@Column('int')
id: number;
@Column('varchar(50)')
name: string;
@Column('datetime')
createdAt: Date;
}
// 验证元数据
const user = new User();
console.log(user.tableMetadata); // { tableName: 'users' }
console.log(user.columnMetadata);
// { id: { type: 'int' }, name: { type: 'varchar(50)' }, createdAt: { type: 'datetime' } }
通过上述练习,可以深入理解装饰器在元数据管理中的核心作用,为后续学习 TypeORM、NestJS 等框架的装饰器应用打下基础。
装饰器作为 TypeScript 元编程的核心工具,其灵活的元数据管理能力使其在框架开发、依赖注入、ORM 映射等场景中发挥重要作用。掌握装饰器的高级应用,能够显著提升代码的可维护性和扩展性。在实际开发中,需注意新旧装饰器的差异,优先采用符合标准的新版装饰器语法。