一、啥是 TypeScript 装饰器
咱先聊聊 TypeScript 装饰器是个啥。简单来说,装饰器就像是给代码穿上一件“魔法外衣”,能在不改变原有代码结构的基础上,给代码添加额外的功能。它是一种特殊的声明,能被附加到类声明、方法、属性或者参数上,来修改类的行为。
比如说,我们有一个简单的类:
// TypeScript 示例
class Person {
constructor(public name: string) {}
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
现在,我们可以用装饰器来给这个类添加一些额外的功能。下面是一个简单的装饰器示例:
// TypeScript 示例
function log(target: any) {
// 保存原始的构造函数
const original = target;
// 新的构造函数
const f: any = function (...args: any[]) {
console.log(`Creating new instance of ${original.name}`);
// 创建实例
return new original(...args);
};
// 复制原型,确保实例仍具有原始类的方法
f.prototype = original.prototype;
return f;
}
@log
class Person {
constructor(public name: string) {}
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person = new Person('John');
person.sayHello();
在这个示例中,log 就是一个装饰器。当我们创建 Person 类的实例时,装饰器会在控制台输出一条日志信息。
二、装饰器的工作原理
装饰器本质上就是一个函数,它接收目标对象作为参数,然后返回一个新的对象。这个新对象可以是修改后的类、方法或者属性等。
1. 类装饰器
类装饰器是应用在类定义上的装饰器。它接收构造函数作为参数,并且可以返回一个新的构造函数来替换原来的构造函数。
// TypeScript 示例
function classDecorator(constructor: Function) {
console.log('Class decorator called');
// 可以在这里修改构造函数的行为
constructor.prototype.newMethod = function () {
console.log('New method added by decorator');
};
}
@classDecorator
class MyClass {
constructor() {
console.log('MyClass constructor called');
}
}
const myObj = new MyClass();
// 调用新方法
(myObj as any).newMethod();
在这个示例中,classDecorator 是一个类装饰器,它给 MyClass 类添加了一个新的方法 newMethod。
2. 方法装饰器
方法装饰器应用在类的方法上。它接收三个参数:目标对象、方法名和方法的描述符。
// TypeScript 示例
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('Method decorator called');
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Before method ${propertyKey} is called`);
const result = originalMethod.apply(this, args);
console.log(`After method ${propertyKey} is called`);
return result;
};
return descriptor;
}
class MyClass {
@methodDecorator
sayHello() {
console.log('Hello!');
}
}
const myObj = new MyClass();
myObj.sayHello();
在这个示例中,methodDecorator 是一个方法装饰器,它在调用 sayHello 方法前后分别输出日志信息。
3. 属性装饰器
属性装饰器应用在类的属性上。它接收两个参数:目标对象和属性名。
// TypeScript 示例
function propertyDecorator(target: any, propertyKey: string) {
console.log('Property decorator called');
let value = target[propertyKey];
const getter = () => {
console.log(`Getting value of ${propertyKey}`);
return value;
};
const setter = (newValue: any) => {
console.log(`Setting value of ${propertyKey} to ${newValue}`);
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class MyClass {
@propertyDecorator
public myProperty: string = 'Initial value';
}
const myObj = new MyClass();
console.log(myObj.myProperty);
myObj.myProperty = 'New value';
在这个示例中,propertyDecorator 是一个属性装饰器,它在获取和设置 myProperty 属性时分别输出日志信息。
三、应用场景
1. 日志记录
就像上面的示例一样,我们可以用装饰器来记录类的创建、方法的调用等信息,方便调试和监控。
// TypeScript 示例
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Method ${propertyKey} called with arguments: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned: ${JSON.stringify(result)}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
const result = calc.add(2, 3);
2. 权限验证
我们可以用装饰器来验证用户是否有访问某个方法的权限。
// TypeScript 示例
function checkPermission(role: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// 模拟权限验证
if (role === 'admin') {
return originalMethod.apply(this, args);
} else {
console.log('You do not have permission to access this method');
return null;
}
};
return descriptor;
};
}
class AdminService {
@checkPermission('admin')
deleteUser(id: number) {
console.log(`Deleting user with id ${id}`);
}
}
const adminService = new AdminService();
adminService.deleteUser(1);
3. 性能监控
我们可以用装饰器来监控方法的执行时间。
// TypeScript 示例
function performanceMonitor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const start = performance.now();
const result = await originalMethod.apply(this, args);
const end = performance.now();
console.log(`Method ${propertyKey} took ${end - start} ms to execute`);
return result;
};
return descriptor;
}
class DataService {
@performanceMonitor
async fetchData() {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
return { data: 'Some data' };
}
}
const dataService = new DataService();
dataService.fetchData();
四、技术优缺点
优点
- 代码复用:装饰器可以将一些通用的功能封装起来,在多个类或者方法中复用,减少代码重复。
- 代码可读性:使用装饰器可以让代码更加简洁,提高代码的可读性。比如在权限验证的示例中,我们只需要在方法上添加一个装饰器,就可以实现权限验证的功能,而不需要在每个方法中都写权限验证的代码。
- 扩展性:装饰器可以很方便地扩展类或者方法的功能,而不需要修改原有的代码。比如我们可以随时添加新的日志记录装饰器或者性能监控装饰器。
缺点
- 调试困难:由于装饰器会修改原有的代码,当出现问题时,调试可能会比较困难。比如在使用装饰器修改方法的行为后,可能很难找到问题出在装饰器代码还是原有的方法代码中。
- 学习成本:对于初学者来说,理解装饰器的工作原理和使用方法可能需要一定的时间和精力。
五、注意事项
- 装饰器的执行顺序:多个装饰器应用在同一个目标上时,它们的执行顺序是从下到上。比如:
// TypeScript 示例
function decorator1(target: any) {
console.log('Decorator 1 called');
return target;
}
function decorator2(target: any) {
console.log('Decorator 2 called');
return target;
}
@decorator1
@decorator2
class MyClass {}
在这个示例中,decorator2 会先执行,然后 decorator1 再执行。
- 装饰器的兼容性:装饰器是 TypeScript 的一个特性,在一些旧版本的 JavaScript 环境中可能不支持。所以在使用装饰器时,需要确保运行环境支持。
六、文章总结
TypeScript 装饰器是一种非常实用的元编程技巧,它可以让我们在不改变原有代码结构的基础上,给代码添加额外的功能。通过类装饰器、方法装饰器和属性装饰器,我们可以实现日志记录、权限验证、性能监控等多种功能。虽然装饰器有一些缺点,比如调试困难和学习成本较高,但它的优点远远大于缺点。在实际开发中,合理使用装饰器可以提高代码的复用性、可读性和扩展性。
评论