TypeScript 装饰器详解:提升类和方法功能(十八)
- 编程语言
- 4天前
- 7热度
- 0评论
装饰器(Decorators)是 TypeScript 中的一项实验性功能,它允许开发者在不修改原有代码的情况下,为类、方法、属性或参数添加额外的功能。装饰器本质上是一个函数,在运行时被调用,可以修改目标对象的行为。本文将详细介绍如何在 TypeScript 中使用装饰器,并提供一些实用的示例。
装饰器简介
装饰器采用 @ 符号作为语法糖,可以附加在类、方法、访问器、属性或参数上。这种模式在框架开发中非常常见,例如 Angular 和 TypeORM 都大量使用装饰器来实现依赖注入、数据验证等功能。
> 注意:装饰器目前是实验性功能,需要在 tsconfig.json 中显式启用。在生产环境中使用时,请确保项目对实验性特性的支持程度。
配置启用装饰器
在使用装饰器之前,需要在 TypeScript 配置文件 tsconfig.json 中启用相关编译选项。
tsconfig.json 配置
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}参数说明:
- experimentalDecorators: 启用装饰器语法支持,这是使用装饰器的前提条件。
- emitDecoratorMetadata: 在编译后的 JavaScript 中生成装饰器的元数据,供依赖注入框架使用。
类装饰器
类装饰器应用于类的构造函数,可以修改类的定义或添加额外的处理逻辑。类装饰器接收一个参数,即目标类的构造函数。
类装饰器基本用法
下面是一个简单的类装饰器示例,展示了如何锁定类的构造函数和原型,防止在运行时添加或删除属性。
// 定义一个类装饰器函数
function sealed(target: Function) {
// 打印装饰器被应用到的类名
console.log("装饰器 applied to: " + target.name);
// 使用 Object.seal 锁定构造函数和原型
Object.seal(target);
Object.seal(target.prototype);
}
// 使用 @ 语法将装饰器应用到类上
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 创建实例测试
const person = new Person("RUNOOB");
console.log("创建: " + person.name);
// 尝试添加新属性(会被阻止,因为类被 seal 了)
// person.age = 25; // 静默失败
// 运行结果:
// 装饰器 applied to: Person
// 创建: RUNOOB类装饰器在类定义时就会执行,通常用于修改类行为、添加元数据或实现 AOP(面向切面编程)。
方法装饰器
方法装饰器应用于类的方法,可以修改方法的属性描述符(Property Descriptor)。方法装饰器接收三个参数:目标对象、属性名称和属性描述符。
方法装饰器基本用法
下面是一个方法装饰器示例,展示了如何设置方法为不可枚举。
// 定义方法装饰器工厂
function enumerable(value: boolean) {
// 返回装饰器函数,接收三个参数
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 修改属性的 enumerable 特性
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
// 应用装饰器,设置该方法为不可枚举
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
const g = new Greeter("World");
// 检查 greet 方法是否可枚举
console.log("方法可枚举: " + g.propertyIsEnumerable("greet"));
// 遍历对象的属性
for (const key in g) {
console.log("属性: " + key);
}
// 运行结果:
// 方法可枚举: falsePropertyDescriptor 包含可枚举(enumerable)、可配置(configurable)、可写(writable)和值(value)等属性,可以根据需要修改。
访问器装饰器
访问器装饰器应用于类的 getter 和 setter 方法。与方法装饰器类似,访问器装饰器也可以修改属性描述符。
访问器装饰器用法
下面是一个访问器装饰器示例,展示了如何锁定 getter。
// 访问器装饰器工厂
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 修改属性的 configurable 特性
descriptor.configurable = value;
};
}
class Point {
private _x: number = 0;
private _y: number = 0;
// 使用装饰器锁定 getter
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
set x(value: number) {
this._x = value;
}
set y(value: number) {
this._y = value;
}
}
const point = new Point();
point.x = 10;
point.y = 20;
console.log("坐标: (" + point.x + ", " + point.y + ")");
// 说明:访问器装饰器不能同时应用于同一个属性的 getter 和 setter,只能选择其中一个。属性装饰器
属性装饰器应用于类的属性定义。属性装饰器接收两个参数:目标对象和属性名称。
属性装饰器用法
下面是一个属性装饰器示例,展示了如何为属性添加元数据。
// 属性装饰器工厂
function format(formatString: string) {
return function (target: any, propertyKey: string) {
// 在目标对象上存储元数据
Object.defineProperty(target, propertyKey + "_format", {
value: formatString,
writable: false,
enumerable: false,
configurable: true
});
};
}
class User {
// 应用属性装饰器,指定日期格式
@format("YYYY-MM-DD")
birthDate: string;
constructor(birthDate: string) {
this.birthDate = birthDate;
}
}
const user = new User("1990-01-01");
console.log("出生日期: " + user.birthDate);
// 访问存储的元数据
console.log("日期格式: " + (user as any).birthDate_format);
// 运行结果:
// 出生日期: 1990-01-01
// 日期格式: YYYY-MM-DD参数装饰器
参数装饰器应用于类方法的参数,可以为参数添加元数据或标记。参数装饰器接收三个参数:目标对象、方法名称和参数在函数中的索引。
参数装饰器用法
下面是一个参数装饰器示例,展示了如何记录参数信息。
// 参数装饰器
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
console.log("参数装饰器: " + propertyKey + " 第 " + (parameterIndex + 1) + " 个参数");
}
class Greeter {
greeting: string;
constructor(greeting: string) {
this.greeting = greeting;
}
// 在参数前使用 @ 语法应用装饰器
greet(@logParameter name: string) {
return this.greeting + ", " + name;
}
}
const greeter = new Greeter("Hello");
greeter.greet("RUNOOB");
// 运行结果:
// 参数装饰器: greet 第 1 个参数装饰器工厂
装饰器工厂是返回装饰器函数的函数。通过装饰器工厂,可以在应用装饰器时传入自定义参数,实现更灵活的配置。
装饰器工厂实现带颜色的日志
下面是一个装饰器工厂示例,展示了如何实现带颜色的日志。
// 装饰器工厂:接收配置参数,返回装饰器函数
function color(colorCode: string) {
// colorCode 是 ANSI 转义序列的颜色代码
// 例如:34 = 蓝色,31 = 红色,32 = 绿色
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始方法
const originalMethod = descriptor.value;
// 重写方法,添加颜色
descriptor.value = function (...args: any[]) {
// 调用原始方法获取返回值
const result = originalMethod.apply(this, args);
// 如果在终端环境,给输出添加颜色
// ANSI 转义序列格式:\x1b[颜色码m 内容 \x1b[0m
return `\x1b[${colorCode}m${result}\x1b[0m`;
};
};
}
class Logger {
// 使用装饰器工厂,传入蓝色代码 34
@color("34")
log(message: string): string {
return message;
}
@color("31")
error(message: string): string {
return message;
}
@color("32")
success(message: string): string {
return message;
}
}
const logger = new Logger();
console.log(logger.log("这是蓝色日志"));
console.log(logger.error("这是红色错误"));
console.log(logger.success("这是绿色成功"));
// 运行结果:
// 这是蓝色日志(终端显示为蓝色)
// 这是红色错误(终端显示为红色)
// 这是绿色成功(终端显示为绿色)装饰器工厂是实际开发中最常用的形式,它允许在应用装饰器时传递参数,实现配置化。
装饰器执行顺序
当一个类上有多个装饰器时,执行顺序遵循特定的规则:
- 装饰器从下往上应用
- 同一类型的多个装饰器从右到左执行
- 参数装饰器先于方法装饰器执行
装饰器执行顺序示例
// 多个装饰器叠加使用
function first() {
console.log("first 装饰器");
return function (target: any) {
console.log("first 装饰器函数");
};
}
function second() {
console.log("second 装饰器");
return function (target: any) {
console.log("second 装饰器函数");
};
}
@first()
@second()
class MyClass {
name: string;
}
const obj = new MyClass();
// 运行结果:
// second 装饰器
// first 装饰器
// second 装饰器函数
// first 装饰器函数装饰器函数先执行定义(console.log),然后按照从下往上的顺序执行装饰器函数。
实际应用场景
装饰器在实际项目中有广泛的应用场景,下面列举几个常见的例子。
日志记录
自动记录方法调用日志。
实例
// 日志装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("调用方法: " + propertyKey + ",参数: " + JSON.stringify(args));
const result = originalMethod.apply(this, args);
console.log("方法返回: " + JSON.stringify(result));
return result;
};
}
class MathService {
@log
add(a: number, b: number): number {
return a + b;
}
@log
multiply(a: number, b: number): number {
return a * b;
}
}
const math = new MathService();
console.log("计算结果: " + math.add(5, 3));
// 运行结果:
// 调用方法: add,参数: [5,3]
// 方法返回: 8
// 计算结果: 8权限验证
实现方法级别的权限检查。
实例
// 模拟当前用户角色
const currentUser = { role: "admin" };
// 权限装饰器
function requireRole(role: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
if (currentUser.role !== role) {
console.log("权限不足,无法执行 " + propertyKey);
return null;
}
return originalMethod.apply(this, args);
};
};
}
class AdminService {
@requireRole("admin")
deleteUser(id: number): string {
return "删除用户 " + id + " 成功";
}
@requireRole("admin")
viewUser(id: number): string {
return "查看用户 " + id + " 信息";
}
}
const adminService = new AdminService();
console.log(adminService.deleteUser(123));
// 运行结果:
// 删除用户 123 成功总结
装饰器是 TypeScript 中一个强大且灵活的特性,可以帮助开发者在不修改原有代码的情况下,为类、方法、属性或参数添加额外的功能。通过本文的介绍,相信你已经掌握了装饰器的基本用法和一些实际应用场景。希望这些知识能帮助你在项目中更好地利用装饰器,提升代码的可维护性和扩展性。